Merge remote branch 'working/user/berick/marc-stream-importer-read-repair'
authorGalen Charlton <gmc@esilibrary.com>
Tue, 11 Oct 2011 13:52:06 +0000 (09:52 -0400)
committerGalen Charlton <gmc@esilibrary.com>
Tue, 11 Oct 2011 13:52:14 +0000 (09:52 -0400)
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
343 files changed:
Open-ILS/examples/apache/eg.conf
Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/opensrf.xml.example
Open-ILS/src/c-apps/oils_auth.c
Open-ILS/src/extras/Makefile.install
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
Open-ILS/src/perlmods/lib/OpenILS/Utils/MFHD.pm
Open-ILS/src/perlmods/lib/OpenILS/Utils/MFHD/Caption.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
Open-ILS/src/reporter/clark-kent.pl
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/100.circ_matrix.sql
Open-ILS/src/sql/Pg/300.schema.staged_search.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/0631.schema.located_uri_visiblity_fix.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0632.data.username-limit-settings.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0633.new_print_method.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0634.security_lockdown.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0635.data.opac.jump-to-details-setting.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0636.data.grace_period_extend.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0637.schema.renewal_checkout_counting.sql [new file with mode: 0644]
Open-ILS/src/templates/conify/global/acq/edi_account.tt2
Open-ILS/src/templates/conify/global/config/coded_value_map.tt2
Open-ILS/src/templates/opac/advanced.tt2
Open-ILS/src/templates/opac/cnbrowse.tt2
Open-ILS/src/templates/opac/home.tt2
Open-ILS/src/templates/opac/login.tt2
Open-ILS/src/templates/opac/mylist.tt2
Open-ILS/src/templates/opac/myopac/prefs.tt2
Open-ILS/src/templates/opac/myopac/update_email.tt2
Open-ILS/src/templates/opac/myopac/update_username.tt2
Open-ILS/src/templates/opac/parts/advanced/global_row.tt2
Open-ILS/src/templates/opac/parts/base.tt2
Open-ILS/src/templates/opac/parts/footer.tt2
Open-ILS/src/templates/opac/parts/goog_analytics.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/homesearch.tt2
Open-ILS/src/templates/opac/parts/js.tt2
Open-ILS/src/templates/opac/parts/myopac/base.tt2
Open-ILS/src/templates/opac/parts/record/authors.tt2
Open-ILS/src/templates/opac/parts/record/body.tt2
Open-ILS/src/templates/opac/parts/record/extras.tt2
Open-ILS/src/templates/opac/parts/record/series.tt2
Open-ILS/src/templates/opac/parts/record/subjects.tt2
Open-ILS/src/templates/opac/parts/record/summary.tt2
Open-ILS/src/templates/opac/parts/result/facets.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/result/table.tt2
Open-ILS/src/templates/opac/parts/searchbar.tt2
Open-ILS/src/templates/opac/parts/topnav.tt2
Open-ILS/src/templates/opac/parts/topnav_logo.tt2
Open-ILS/src/templates/opac/place_hold.tt2
Open-ILS/src/templates/opac/record.tt2
Open-ILS/src/templates/opac/results.tt2
Open-ILS/web/css/skin/default/opac/style.css
Open-ILS/web/images/adv_search_minus_btn.png [new file with mode: 0644]
Open-ILS/web/images/adv_search_plus_btn.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-left1.png [deleted file]
Open-ILS/web/images/dash-corner-left2.png [deleted file]
Open-ILS/web/images/dash-corner-mid1.png [deleted file]
Open-ILS/web/images/dash-corner-mid2.png [deleted file]
Open-ILS/web/images/dash-corner-right1.png [deleted file]
Open-ILS/web/images/dash-corner-right2.png [deleted file]
Open-ILS/web/images/dash-divider.jpg [deleted file]
Open-ILS/web/images/facet_box_bg.png [new file with mode: 0644]
Open-ILS/web/images/facet_box_bg_bottom.png [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/common/li_table.js
Open-ILS/web/js/ui/default/acq/common/li_table_pager.js
Open-ILS/web/js/ui/default/acq/search/unified.js
Open-ILS/web/js/ui/default/actor/user/register.js
Open-ILS/web/opac/common/js/org_utils.js
Open-ILS/web/opac/images/eg_tiny_logo.png [new file with mode: 0644]
Open-ILS/web/opac/images/main_logo.png [new file with mode: 0644]
Open-ILS/web/opac/images/small_logo.png [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/opac/locale/en-US/opac.dtd
Open-ILS/web/opac/skin/default/js/myopac.js
Open-ILS/web/opac/skin/default/js/rdetail.js
Open-ILS/web/opac/skin/default/xml/myopac/myopac_summary.xml
Open-ILS/web/opac/skin/default/xml/page_rdetail.xml
Open-ILS/xul/staff_client/chrome/content/OpenILS/util_overlay_chrome.xul
Open-ILS/xul/staff_client/chrome/content/OpenILS/util_overlay_offline.xul
Open-ILS/xul/staff_client/chrome/content/auth/controller.js
Open-ILS/xul/staff_client/chrome/content/auth/session.js
Open-ILS/xul/staff_client/chrome/content/main/about.html
Open-ILS/xul/staff_client/chrome/content/main/main.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/content/util/print.js
Open-ILS/xul/staff_client/chrome/content/util/print_win.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/OpenILS/util_overlay.xul
Open-ILS/xul/staff_client/server/cat/spine_labels.js
Open-ILS/xul/staff_client/server/cat/volume_copy_creator.js
Open-ILS/xul/staff_client/server/patron/place_hold.js
Open-ILS/xul/staff_client/server/skin/custom.js.example
README
build/i18n/po/AutoFieldWidget.js/cs-CZ.po
build/i18n/po/AutoFieldWidget.js/en-CA.po
build/i18n/po/AutoFieldWidget.js/en-GB.po
build/i18n/po/AutoFieldWidget.js/es-ES.po
build/i18n/po/AutoFieldWidget.js/fr-CA.po
build/i18n/po/AutoFieldWidget.js/hy-AM.po
build/i18n/po/AutoFieldWidget.js/pt-BR.po
build/i18n/po/AutoFieldWidget.js/ru-RU.po
build/i18n/po/TranslatorPopup.js/cs-CZ.po
build/i18n/po/TranslatorPopup.js/en-CA.po
build/i18n/po/TranslatorPopup.js/en-GB.po
build/i18n/po/TranslatorPopup.js/es-ES.po
build/i18n/po/TranslatorPopup.js/fr-CA.po
build/i18n/po/TranslatorPopup.js/hy-AM.po
build/i18n/po/TranslatorPopup.js/pt-BR.po
build/i18n/po/TranslatorPopup.js/ru-RU.po
build/i18n/po/User.js/cs-CZ.po
build/i18n/po/User.js/en-CA.po
build/i18n/po/User.js/en-GB.po
build/i18n/po/User.js/es-ES.po
build/i18n/po/User.js/fr-CA.po
build/i18n/po/User.js/hy-AM.po
build/i18n/po/User.js/pt-BR.po
build/i18n/po/User.js/ru-RU.po
build/i18n/po/XULTermLoader.js/cs-CZ.po
build/i18n/po/XULTermLoader.js/en-CA.po
build/i18n/po/XULTermLoader.js/en-GB.po
build/i18n/po/XULTermLoader.js/es-ES.po
build/i18n/po/XULTermLoader.js/fr-CA.po
build/i18n/po/XULTermLoader.js/hy-AM.po
build/i18n/po/XULTermLoader.js/pt-BR.po
build/i18n/po/XULTermLoader.js/ru-RU.po
build/i18n/po/acq.js/cs-CZ.po
build/i18n/po/acq.js/en-CA.po
build/i18n/po/acq.js/en-GB.po
build/i18n/po/acq.js/es-ES.po
build/i18n/po/acq.js/fr-CA.po
build/i18n/po/acq.js/hy-AM.po
build/i18n/po/acq.js/pt-BR.po
build/i18n/po/acq.js/ru-RU.po
build/i18n/po/admin.properties/cs-CZ.po
build/i18n/po/admin.properties/en-CA.po
build/i18n/po/admin.properties/en-GB.po
build/i18n/po/admin.properties/es-ES.po
build/i18n/po/admin.properties/fr-CA.po
build/i18n/po/admin.properties/hy-AM.po
build/i18n/po/admin.properties/pt-BR.po
build/i18n/po/admin.properties/ru-RU.po
build/i18n/po/auth.properties/cs-CZ.po
build/i18n/po/auth.properties/en-CA.po
build/i18n/po/auth.properties/en-GB.po
build/i18n/po/auth.properties/es-ES.po
build/i18n/po/auth.properties/fr-CA.po
build/i18n/po/auth.properties/hy-AM.po
build/i18n/po/auth.properties/pt-BR.po
build/i18n/po/auth.properties/ru-RU.po
build/i18n/po/capture.js/cs-CZ.po
build/i18n/po/capture.js/en-CA.po
build/i18n/po/capture.js/en-GB.po
build/i18n/po/capture.js/es-ES.po
build/i18n/po/capture.js/fr-CA.po
build/i18n/po/capture.js/hy-AM.po
build/i18n/po/capture.js/pt-BR.po
build/i18n/po/capture.js/ru-RU.po
build/i18n/po/cat.properties/cs-CZ.po
build/i18n/po/cat.properties/en-CA.po
build/i18n/po/cat.properties/en-GB.po
build/i18n/po/cat.properties/es-ES.po
build/i18n/po/cat.properties/fr-CA.po
build/i18n/po/cat.properties/hy-AM.po
build/i18n/po/cat.properties/pt-BR.po
build/i18n/po/cat.properties/ru-RU.po
build/i18n/po/circ.properties/cs-CZ.po
build/i18n/po/circ.properties/en-CA.po
build/i18n/po/circ.properties/en-GB.po
build/i18n/po/circ.properties/es-ES.po
build/i18n/po/circ.properties/fr-CA.po
build/i18n/po/circ.properties/hy-AM.po
build/i18n/po/circ.properties/pt-BR.po
build/i18n/po/circ.properties/ru-RU.po
build/i18n/po/common.properties/cs-CZ.po
build/i18n/po/common.properties/en-CA.po
build/i18n/po/common.properties/en-GB.po
build/i18n/po/common.properties/es-ES.po
build/i18n/po/common.properties/fr-CA.po
build/i18n/po/common.properties/hy-AM.po
build/i18n/po/common.properties/pt-BR.po
build/i18n/po/common.properties/ru-RU.po
build/i18n/po/conify.dtd/cs-CZ.po
build/i18n/po/conify.dtd/en-CA.po
build/i18n/po/conify.dtd/en-GB.po
build/i18n/po/conify.dtd/es-ES.po
build/i18n/po/conify.dtd/fr-CA.po
build/i18n/po/conify.dtd/hy-AM.po
build/i18n/po/conify.dtd/pt-BR.po
build/i18n/po/conify.dtd/ru-RU.po
build/i18n/po/conify.js/cs-CZ.po
build/i18n/po/conify.js/en-CA.po
build/i18n/po/conify.js/en-GB.po
build/i18n/po/conify.js/es-ES.po
build/i18n/po/conify.js/fr-CA.po
build/i18n/po/conify.js/hy-AM.po
build/i18n/po/conify.js/pt-BR.po
build/i18n/po/conify.js/ru-RU.po
build/i18n/po/db.seed/cs-CZ.po
build/i18n/po/db.seed/en-CA.po
build/i18n/po/db.seed/en-GB.po
build/i18n/po/db.seed/es-ES.po
build/i18n/po/db.seed/fr-CA.po
build/i18n/po/db.seed/hy-AM.po
build/i18n/po/db.seed/pt-BR.po
build/i18n/po/db.seed/ru-RU.po
build/i18n/po/fm_IDL.dtd/cs-CZ.po
build/i18n/po/fm_IDL.dtd/en-CA.po
build/i18n/po/fm_IDL.dtd/en-GB.po
build/i18n/po/fm_IDL.dtd/es-ES.po
build/i18n/po/fm_IDL.dtd/fr-CA.po
build/i18n/po/fm_IDL.dtd/hy-AM.po
build/i18n/po/fm_IDL.dtd/pt-BR.po
build/i18n/po/fm_IDL.dtd/ru-RU.po
build/i18n/po/ils_events.xml/cs-CZ.po
build/i18n/po/ils_events.xml/en-CA.po
build/i18n/po/ils_events.xml/en-GB.po
build/i18n/po/ils_events.xml/es-ES.po
build/i18n/po/ils_events.xml/fr-CA.po
build/i18n/po/ils_events.xml/hy-AM.po
build/i18n/po/ils_events.xml/pt-BR.po
build/i18n/po/ils_events.xml/ru-RU.po
build/i18n/po/lang.dtd/ar-AR.po
build/i18n/po/lang.dtd/cs-CZ.po
build/i18n/po/lang.dtd/en-CA.po
build/i18n/po/lang.dtd/en-GB.po
build/i18n/po/lang.dtd/es-ES.po
build/i18n/po/lang.dtd/fr-CA.po
build/i18n/po/lang.dtd/hy-AM.po
build/i18n/po/lang.dtd/pt-BR.po
build/i18n/po/lang.dtd/ru-RU.po
build/i18n/po/multiclass_search_help.html/cs-CZ.po
build/i18n/po/multiclass_search_help.html/en-CA.po
build/i18n/po/multiclass_search_help.html/en-GB.po
build/i18n/po/multiclass_search_help.html/es-ES.po
build/i18n/po/multiclass_search_help.html/fr-CA.po
build/i18n/po/multiclass_search_help.html/hy-AM.po
build/i18n/po/multiclass_search_help.html/pt-BR.po
build/i18n/po/multiclass_search_help.html/ru-RU.po
build/i18n/po/offline.properties/cs-CZ.po
build/i18n/po/offline.properties/en-CA.po
build/i18n/po/offline.properties/en-GB.po
build/i18n/po/offline.properties/es-ES.po
build/i18n/po/offline.properties/fr-CA.po
build/i18n/po/offline.properties/hy-AM.po
build/i18n/po/offline.properties/pt-BR.po
build/i18n/po/offline.properties/ru-RU.po
build/i18n/po/opac.dtd/cs-CZ.po
build/i18n/po/opac.dtd/en-CA.po
build/i18n/po/opac.dtd/en-GB.po
build/i18n/po/opac.dtd/es-ES.po
build/i18n/po/opac.dtd/fr-CA.po
build/i18n/po/opac.dtd/hy-AM.po
build/i18n/po/opac.dtd/pt-BR.po
build/i18n/po/opac.dtd/ru-RU.po
build/i18n/po/opac.js/cs-CZ.po
build/i18n/po/opac.js/en-CA.po
build/i18n/po/opac.js/en-GB.po
build/i18n/po/opac.js/es-ES.po
build/i18n/po/opac.js/fr-CA.po
build/i18n/po/opac.js/hy-AM.po
build/i18n/po/opac.js/pt-BR.po
build/i18n/po/opac.js/ru-RU.po
build/i18n/po/patron.properties/cs-CZ.po
build/i18n/po/patron.properties/en-CA.po
build/i18n/po/patron.properties/en-GB.po
build/i18n/po/patron.properties/es-ES.po
build/i18n/po/patron.properties/fr-CA.po
build/i18n/po/patron.properties/hy-AM.po
build/i18n/po/patron.properties/pt-BR.po
build/i18n/po/patron.properties/ru-RU.po
build/i18n/po/pickup_and_return.js/cs-CZ.po
build/i18n/po/pickup_and_return.js/en-CA.po
build/i18n/po/pickup_and_return.js/en-GB.po
build/i18n/po/pickup_and_return.js/es-ES.po
build/i18n/po/pickup_and_return.js/fr-CA.po
build/i18n/po/pickup_and_return.js/hy-AM.po
build/i18n/po/pickup_and_return.js/pt-BR.po
build/i18n/po/pickup_and_return.js/ru-RU.po
build/i18n/po/pull_list.js/cs-CZ.po
build/i18n/po/pull_list.js/en-CA.po
build/i18n/po/pull_list.js/en-GB.po
build/i18n/po/pull_list.js/es-ES.po
build/i18n/po/pull_list.js/fr-CA.po
build/i18n/po/pull_list.js/hy-AM.po
build/i18n/po/pull_list.js/pt-BR.po
build/i18n/po/pull_list.js/ru-RU.po
build/i18n/po/register.js/cs-CZ.po
build/i18n/po/register.js/en-CA.po
build/i18n/po/register.js/en-GB.po
build/i18n/po/register.js/es-ES.po
build/i18n/po/register.js/fr-CA.po
build/i18n/po/register.js/hy-AM.po
build/i18n/po/register.js/pt-BR.po
build/i18n/po/register.js/ru-RU.po
build/i18n/po/reports.dtd/cs-CZ.po
build/i18n/po/reports.dtd/en-CA.po
build/i18n/po/reports.dtd/en-GB.po
build/i18n/po/reports.dtd/es-ES.po
build/i18n/po/reports.dtd/fr-CA.po
build/i18n/po/reports.dtd/hy-AM.po
build/i18n/po/reports.dtd/pt-BR.po
build/i18n/po/reports.dtd/ru-RU.po
build/i18n/po/reports.js/cs-CZ.po
build/i18n/po/reports.js/en-CA.po
build/i18n/po/reports.js/en-GB.po
build/i18n/po/reports.js/es-ES.po
build/i18n/po/reports.js/fr-CA.po
build/i18n/po/reports.js/hy-AM.po
build/i18n/po/reports.js/pt-BR.po
build/i18n/po/reports.js/ru-RU.po
build/i18n/po/reservation.js/cs-CZ.po
build/i18n/po/reservation.js/en-CA.po
build/i18n/po/reservation.js/en-GB.po
build/i18n/po/reservation.js/es-ES.po
build/i18n/po/reservation.js/fr-CA.po
build/i18n/po/reservation.js/hy-AM.po
build/i18n/po/reservation.js/pt-BR.po
build/i18n/po/reservation.js/ru-RU.po
build/i18n/po/selfcheck.js/cs-CZ.po
build/i18n/po/selfcheck.js/en-CA.po
build/i18n/po/selfcheck.js/en-GB.po
build/i18n/po/selfcheck.js/es-ES.po
build/i18n/po/selfcheck.js/fr-CA.po
build/i18n/po/selfcheck.js/hy-AM.po
build/i18n/po/selfcheck.js/pt-BR.po
build/i18n/po/selfcheck.js/ru-RU.po
build/i18n/po/vandelay.dtd/cs-CZ.po
build/i18n/po/vandelay.dtd/en-CA.po
build/i18n/po/vandelay.dtd/en-GB.po
build/i18n/po/vandelay.dtd/es-ES.po
build/i18n/po/vandelay.dtd/fr-CA.po
build/i18n/po/vandelay.dtd/hy-AM.po
build/i18n/po/vandelay.dtd/pt-BR.po
build/i18n/po/vandelay.dtd/ru-RU.po
build/tools/update_db.sh

index dbcd0c4..77b5f40 100644 (file)
@@ -102,7 +102,7 @@ ExpiresByType text/css "access plus 50 minutes"
 # ----------------------------------------------------------------------------------
 # Set up our SSL virtual host
 # ----------------------------------------------------------------------------------
-Listen 443
+#Listen 443
 NameVirtualHost *:443
 <VirtualHost *:443>
        DocumentRoot "/openils/var/web"
index 54a352c..f205fd9 100644 (file)
@@ -5177,7 +5177,7 @@ SELECT  usr,
                        <field reporter:label="Edit Date" name="edit_date" reporter:datatype="timestamp"/>
                        <field reporter:label="Name" name="name" reporter:datatype="text"/>
                        <field reporter:label="Circ Lib" name="circ_lib" reporter:datatype="link"/>
-                       <field reporter:label="Status" name="status" reporter:datatype="int"/>
+                       <field reporter:label="Status" name="status" reporter:datatype="link"/>
                        <field reporter:label="Location" name="location" reporter:datatype="link"/>
                        <field reporter:label="Loan Duration" name="loan_duration" reporter:datatype="int"/>
                        <field reporter:label="Fine Level" name="fine_level" reporter:datatype="int"/>
@@ -5200,6 +5200,7 @@ SELECT  usr,
                        <link field="creator" reltype="has_a" key="id" map="" class="au"/>
                        <link field="editor" reltype="has_a" key="id" map="" class="au"/>
                        <link field="circ_lib" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="status" reltype="has_a" key="id" map="" class="ccs"/>
                        <link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
                        <link field="location" reltype="has_a" key="id" map="" class="acpl"/>
                </links>
index 0b16511..f010779 100644 (file)
@@ -430,6 +430,11 @@ vim:et:ts=4:sw=4:
                         <temp>300</temp>
                         <persist>2 weeks</persist>
                     </default_timeout>
+                    <auth_limits>
+                        <seed>30</seed> <!-- amount of time a seed request is valid for -->
+                        <block_time>90</block_time> <!-- amount of time since last auth or seed request to save failure counts -->
+                        <block_count>10</block_count> <!-- number of failures before blocking access -->
+                    </auth_limits>
                 </app_settings>
             </open-ils.auth>
 
index a4e30da..8e1c028 100644 (file)
@@ -8,6 +8,7 @@
 #include "openils/oils_event.h"
 
 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
+#define OILS_AUTH_COUNT_SFFX "_count"
 
 #define MODULENAME "open-ils.auth"
 
@@ -26,6 +27,9 @@ static long _oilsAuthOPACTimeout = 0;
 static long _oilsAuthStaffTimeout = 0;
 static long _oilsAuthOverrideTimeout = 0;
 static long _oilsAuthPersistTimeout = 0;
+static long _oilsAuthSeedTimeout = 0;
+static long _oilsAuthBlockTimeout = 0;
+static long _oilsAuthBlockCount = 0;
 
 
 /**
@@ -83,6 +87,42 @@ int osrfAppInitialize() {
                "if found, otherwise returns the NO_SESSION event"
                "PARAMS( authToken )", 1, 0 );
 
+       if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
+
+               jsonObject* value_obj;
+
+               value_obj = osrf_settings_host_value_object(
+                       "/apps/open-ils.auth/app_settings/auth_limits/seed" );
+               _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
+               jsonObjectFree(value_obj);
+               if( -1 == _oilsAuthSeedTimeout ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
+                       _oilsAuthSeedTimeout = 30;
+               }
+
+               value_obj = osrf_settings_host_value_object(
+                       "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
+               _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
+               jsonObjectFree(value_obj);
+               if( -1 == _oilsAuthBlockTimeout ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
+                       _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
+               }
+
+               value_obj = osrf_settings_host_value_object(
+                       "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
+               _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
+               jsonObjectFree(value_obj);
+               if( -1 == _oilsAuthBlockCount ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
+                       _oilsAuthBlockCount = 10;
+               }
+
+               osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
+                       "seed => %ld : block_timeout => %ld : block_count => %ld",
+                       _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
+       }
+
        return 0;
 }
 
@@ -131,8 +171,14 @@ int oilsAuthInit( osrfMethodContext* ctx ) {
 
                        // Build a key and a seed; store them in memcache.
                        char* key  = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
+                       char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
                        char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
-                       osrfCachePutString( key, seed, 30 );
+                       jsonObject* countobject = osrfCacheGetObject( countkey );
+                       if(!countobject) {
+                               countobject = jsonNewNumberObject( (double) 0 );
+                       }
+                       osrfCachePutString( key, seed, _oilsAuthSeedTimeout );
+                       osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
 
                        osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
 
@@ -141,6 +187,8 @@ int oilsAuthInit( osrfMethodContext* ctx ) {
 
                        free( seed );
                        free( key );
+                       free( countkey );
+                       jsonObjectFree( countobject );
                }
 
                // Return the seed to the client.
@@ -229,6 +277,9 @@ static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
                        " (check that memcached is running and can be connected to) "
                );
        }
+    
+       // We won't be needing the seed again, remove it
+       osrfCacheRemove( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
 
        // Get the hashed password from the user object
        char* realPassword = oilsFMGetString( userObj, "passwd" );
@@ -257,6 +308,23 @@ static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
 
        free(maskedPw);
 
+       char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
+       jsonObject* countobject = osrfCacheGetObject( countkey );
+       if(countobject) {
+               long failcount = (long) jsonObjectGetNumber( countobject );
+               if(failcount >= _oilsAuthBlockCount) {
+                       ret = 0;
+                   osrfLogInternal(OSRF_LOG_MARK, "oilsAuth found too many recent failures: %i, forcing failure state.", failcount);
+               }
+               if(ret == 0) {
+                       failcount += 1;
+               }
+               jsonObjectSetNumber( countobject, failcount );
+               osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
+               jsonObjectFree(countobject);
+       }
+       free(countkey);
+
        return ret;
 }
 
index c4cf63f..c5e0a0e 100644 (file)
@@ -65,9 +65,9 @@ FEDORA=$(shell uname -r | grep "\.fc[0-9][0-9]\.")
 FEDORA_64=$(shell uname -r | grep "\.fc[0-9][0-9]\.x86_64")
 
 #RHEL/Centos PGSQL 
-PGSQL_HOST=http://yum.pgsqlrpms.org/reporpms/8.4
-PGSQL_CENTOS=pgdg-centos-8.4-2.noarch.rpm
-PGSQL_REDHAT=pgdg-redhat-8.4-2.noarch.rpm      
+PGSQL_HOST=http://yum.pgsqlrpms.org/reporpms/9.0
+PGSQL_CENTOS=pgdg-centos-9.0-2.noarch.rpm
+PGSQL_REDHAT=pgdg-redhat-9.0-2.noarch.rpm      
 
 # Debian dependencies
 DEBS =  \
@@ -228,12 +228,14 @@ DEB_APACHE_DISMODS = \
     deflate
 
 # Chronically unpackaged CPAN modules
+# Note older version of Net::Z3950::SimpleServer to avoid yaz 4.1.x dependency
 CPAN_MODULES = \
-    Business::EDI \
-    Library::CallNumber::LC \
-    Net::Z3950::Simple2ZOOM \
+       Business::EDI \
+       Library::CallNumber::LC \
+       M/MI/MIRK/Net-Z3950-SimpleServer-1.12.tar.gz \
+       Net::Z3950::Simple2ZOOM \
        Template::Plugin::POSIX \
-    SRU
+       SRU
 
 # More chronically unpackaged CPAN modules (available in Squeeze though)
 CPAN_MODULES_MORE = \
@@ -441,7 +443,7 @@ install_fedora_rpms:
        yum -y install $(FEDORA_RPMS)
 
 install_fedora_pgsql_server:
-       yum -y install $(PGSQL_84_RPMS)
+       yum -y install $(PGSQL_90_RPMS)
 
 # CENTOS
 install_centos_rpms:
@@ -462,14 +464,14 @@ install_centos_pgsql:
        wget $(PGSQL_HOST)/$(PGSQL_CENTOS)
        rpm -Uvh --force ./$(PGSQL_CENTOS)
        yum update -y
-       yum -y install $(PGSQL_84_RPMS)
+       yum -y install $(PGSQL_90_RPMS)
 
 install_redhat_pgsql:
        if [ $(LBITS) -eq 64 ]; then yum remove -y postgresql-libs-8.1*i386 apr-util-devel-*i386 ; fi;
        wget $(PGSQL_HOST)/$(PGSQL_REDHAT)
        rpm -Uvh --force ./$(PGSQL_REDHAT)
        yum update -y
-       yum -y install $(PGSQL_84_RPMS)
+       yum -y install $(PGSQL_90_RPMS)
 
 install_centos_perl:
        for m in $(CENTOS_PERL); do perl -MCPAN -e "install \"$$m\";"; done
index 4710d81..ebb56a1 100644 (file)
@@ -1259,9 +1259,10 @@ __PACKAGE__->register_method(
         desc   => "Update the operator's username", 
         params => [
             { desc => 'Authentication token', type => 'string' },
-            { desc => 'New username',         type => 'string' }
+            { desc => 'New username',         type => 'string' },
+            { desc => 'Current password',     type => 'string' }
         ],
-        return => {desc => '1 on success, Event on error'}
+        return => {desc => '1 on success, Event on error or incorrect current password'}
     }
 );
 
@@ -1272,9 +1273,10 @@ __PACKAGE__->register_method(
         desc   => "Update the operator's email address", 
         params => [
             { desc => 'Authentication token', type => 'string' },
-            { desc => 'New email address',    type => 'string' }
+            { desc => 'New email address',    type => 'string' },
+            { desc => 'Current password',     type => 'string' }
         ],
-        return => {desc => '1 on success, Event on error'}
+        return => {desc => '1 on success, Event on error or incorrect current password'}
     }
 );
 
@@ -1287,12 +1289,14 @@ sub update_passwd {
         or return $e->die_event;
     my $api = $self->api_name;
 
+    # make sure the original password matches the in-database password
+    if (md5_hex($orig_pw) ne $db_user->passwd) {
+        $e->rollback;
+        return new OpenILS::Event('INCORRECT_PASSWORD');
+    }
+
     if( $api =~ /password/o ) {
-        # make sure the original password matches the in-database password
-        if (md5_hex($orig_pw) ne $db_user->passwd) {
-            $e->rollback;
-            return new OpenILS::Event('INCORRECT_PASSWORD');
-        }
+
         $db_user->passwd($new_val);
 
     } else {
index 058bce0..71a2020 100644 (file)
@@ -9,6 +9,7 @@ use OpenILS::Utils::Fieldmapper;
 use OpenILS::Const qw/:const/;
 use OpenSRF::AppSession;
 use OpenILS::Event;
+use OpenILS::Utils::Penalty;
 use OpenILS::Application::Circ::CircCommon;
 my $U = 'OpenILS::Application::AppUtils';
 
index 1319201..9daa10c 100644 (file)
@@ -18,7 +18,9 @@ my $U = "OpenILS::Application::AppUtils";
 
 # -----------------------------------------------------------------
 # Voids overdue fines on the given circ.  if a backdate is 
-# provided, then we only void back to the backdate
+# provided, then we only void back to the backdate, unless the
+# backdate is to within the grace period, in which case we void all
+# overdue fines.
 # -----------------------------------------------------------------
 sub void_overdues {
     my($class, $e, $circ, $backdate, $note) = @_;
@@ -43,9 +45,15 @@ sub void_overdues {
         my $interval = OpenSRF::Utils->interval_to_seconds($duration);
 
         my $date = DateTime::Format::ISO8601->parse_datetime($backdate);
-        $backdate = $U->epoch2ISO8601($date->epoch + $interval);
-        $logger->info("applying backdate $backdate in overdue voiding");
-        $$bill_search{billing_ts} = {'>=' => $backdate};
+        my $due_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->due_date))->epoch;
+        my $grace_period = extend_grace_period( $class, $circ->circ_lib, $circ->due_date, OpenSRF::Utils->interval_to_seconds($circ->grace_period), $e);
+        if($date->epoch < $due_date + $grace_period) {
+            $logger->info("backdate $backdate is within grace period, voiding all");
+        } else {
+            $backdate = $U->epoch2ISO8601($date->epoch + $interval);
+            $logger->info("applying backdate $backdate in overdue voiding");
+            $$bill_search{billing_ts} = {'>=' => $backdate};
+        }
     }
 
     my $bills = $e->search_money_billing($bill_search);
@@ -106,4 +114,99 @@ sub create_bill {
        return undef;
 }
 
+sub extend_grace_period {
+    my($class, $circ_lib, $due_date, $grace_period, $e, $h) = @_;
+    if ($grace_period >= 86400) { # Only extend grace periods greater than or equal to a full day
+        my $parser = DateTime::Format::ISO8601->new;
+        my $due_dt = $parser->parse_datetime( cleanse_ISO8601( $due_date ) );
+        my $due = $due_dt->epoch;
+
+        my $grace_extend = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend');
+        $e = new_editor() if (!$e);
+        $h = $e->retrieve_actor_org_unit_hours_of_operation($circ_lib) if (!$h);
+        if ($grace_extend and $h) { 
+            my $new_grace_period = $grace_period;
+
+            $logger->info( "Circ lib has an hours-of-operation entry and grace period extension is enabled." );
+
+            my $closed = 0;
+            my %h_closed = {};
+            for my $i (0 .. 6) {
+                my $dow_open = "dow_${i}_open";
+                my $dow_close = "dow_${i}_close";
+                if($h->$dow_open() eq '00:00:00' and $h->$dow_close() eq '00:00:00') {
+                    $closed++;
+                    $h_closed{$i} = 1;
+                } else {
+                    $h_closed{$i} = 0;
+                }
+            }
+
+            if($closed == 7) {
+                $logger->info("Circ lib is closed all week according to hours-of-operation entry. Skipping grace period extension checks.");
+            } else {
+                # Extra nice grace periods
+                # AKA, merge closed dates trailing the grace period into the grace period
+                my $grace_extend_into_closed = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend.into_closed');
+                $due += 86400 if $grace_extend_into_closed;
+
+                my $grace_extend_all = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend.all');
+
+                if ( $grace_extend_all ) {
+                    # Start checking the day after the item was due
+                    # This is "The grace period only counts open days"
+                    # NOTE: Adding 86400 seconds is not the same as adding one day. This uses seconds intentionally.
+                    $due_dt = $due_dt->add( seconds => 86400 );
+                } else {
+                    # Jump to the end of the grace period
+                    # This is "If the grace period ends on a closed day extend it"
+                    # NOTE: This adds grace period as a number of seconds intentionally
+                    $due_dt = $due_dt->add( seconds => $grace_period );
+                }
+
+                my $count = 0; # Infinite loop protection
+                do {
+                    $closed = 0; # Starting assumption for day: We are not closed
+                    $count++; # We limit the number of loops below.
+
+                    # get the day of the week for the day we are looking at
+                    my $dow = $due_dt->day_of_week_0;
+
+                    # Check hours of operation first.
+                    if ($h_closed{$dow}) {
+                        $closed = 1;
+                        $new_grace_period += 86400;
+                        $due_dt->add( seconds => 86400 );
+                    } else {
+                        # Check for closed dates for this period
+                        my $timestamptz = $due_dt->strftime('%FT%T%z');
+                        my $cl = $e->search_actor_org_unit_closed_date(
+                                { close_start => { '<=' => $timestamptz },
+                                  close_end   => { '>=' => $timestamptz },
+                                  org_unit    => $circ_lib }
+                        );
+                        if ($cl and @$cl) {
+                            $closed = 1;
+                            foreach (@$cl) {
+                                my $cl_dt = $parser->parse_datetime( cleanse_ISO8601( $_->close_end ) );
+                                while ($due_dt <= $cl_dt) {
+                                    $due_dt->add( seconds => 86400 );
+                                    $new_grace_period += 86400;
+                                }
+                            }
+                        } else {
+                            $due_dt->add( seconds => 86400 );
+                        }
+                    }
+                } while ( $count <= 366 and ( $closed or $due_dt->epoch <= $due + $new_grace_period ) );
+                if ($new_grace_period > $grace_period) {
+                    $grace_period = $new_grace_period;
+                    $logger->info( "Grace period for circ extended to $grace_period [" . seconds_to_interval( $grace_period ) . "]" );
+                }
+            }
+        }
+    }
+    return $grace_period;
+}
+
 1;
index ac8046b..3423932 100644 (file)
@@ -1512,7 +1512,7 @@ sub cache_facets {
             },
             where   => {
                 '+mmrsm' => { $count_field => $results },
-                '+cmf'   => { field_class => { 'not in' => $ignore } }
+                '+cmf'   => { field_class => { 'not in' => $ignore }, facet_field => 't' }
             }
         }
     );
index 6a2b6d6..eff589c 100644 (file)
@@ -13,6 +13,7 @@ use DateTime;
 use DateTime::Format::ISO8601;
 use OpenILS::Utils::Penalty;
 use POSIX qw(ceil);
+use OpenILS::Application::Circ::CircCommon;
 
 sub isTrue {
        my $v = shift;
@@ -886,38 +887,7 @@ sub generate_fines {
                                $log->info( "Potential first billing for circ ".$c->id );
                                $last_fine = $due;
 
-                               if (0) {
-                                       if (my $h = $hoo{$c->$circ_lib_method}) { 
-
-                                               $log->info( "Circ lib has an hours-of-operation entry" );
-                                               # find the day after the due date...
-                                               $due_dt = $due_dt->add( days => 1 );
-
-                                               # get the day of the week for that day...
-                                               my $dow = $due_dt->day_of_week_0;
-                                               my $dow_open = "dow_${dow}_open";
-                                               my $dow_close = "dow_${dow}_close";
-
-                                               my $count = 0;
-                                               while ( $h->$dow_open eq '00:00:00' and $h->$dow_close eq '00:00:00' ) {
-                                                       # if the circ lib is closed, add a day to the grace period...
-
-                                                       $grace_period+=86400;
-                                                       $log->info( "Grace period for circ ".$c->id." extended to $grace_period [" . seconds_to_interval( $grace_period ) . "]" );
-                                                       $log->info( "Day of week $dow open $dow_open, close $dow_close" );
-
-                                                       $due_dt = $due_dt->add( days => 1 );
-                                                       $dow = $due_dt->day_of_week_0;
-                                                       $dow_open = "dow_${dow}_open";
-                                                       $dow_close = "dow_${dow}_close";
-
-                                                       $count++;
-
-                                                       # and check for up to a week
-                                                       last if ($count > 6);
-                                               }
-                                       }
-                               }
+                               $grace_period = OpenILS::Application::Circ::CircCommon->extend_grace_period($c->$circ_lib_method->to_fieldmapper->id,$c->$due_date_method,$grace_period,undef,$hoo{$c->$circ_lib_method});
                        }
 
             next if ($last_fine > $now);
index 6964c56..309f9fe 100644 (file)
@@ -358,6 +358,12 @@ sub get_compressed_holdings {
     my $opts = shift;
     my $skip_sort = $opts->{'skip_sort'};
 
+    # basic check for necessary pattern information
+    if (!scalar keys %{$caption->pattern}) {
+        carp "Cannot compress without pattern data, returning original holdings";
+        return $self->holdings_by_caption($caption);
+    }
+
     # make sure none are compressed (except for open-ended)
     my @decomp_holdings;
     if ($skip_sort) {
index 9445834..d949b4e 100644 (file)
@@ -128,6 +128,12 @@ sub decode_pattern {
     # XXX WRITE ME (?)
 }
 
+sub pattern {
+    my $self = shift;
+
+    return $self->{_mfhdc_PATTERN};
+}
+
 sub compressible {
     my $self = shift;
 
index 726db95..b63f89a 100644 (file)
@@ -91,6 +91,25 @@ sub load_myopac_prefs {
     $self->prepare_extended_user_info;
     my $user = $self->ctx->{user};
 
+    my $lock_usernames = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.lock_usernames');
+    if($lock_usernames == 1) {
+        # Policy says no username changes
+        $self->ctx->{username_change_disallowed} = 1;
+    } else {
+        my $username_unlimit = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.unlimit_usernames');
+        if($username_unlimit != 1) {
+            my $regex_check = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.barcode_regex');
+            if(!$regex_check) {
+                # Default is "starts with a number"
+                $regex_check = '^\d+';
+            }
+            # You already have a username?
+            if($regex_check and $self->ctx->{user}->usrname !~ /$regex_check/) {
+                $self->ctx->{username_change_disallowed} = 1;
+            }
+        }
+    }
+
     return Apache2::Const::OK unless 
         $pending_addr or $replace_addr or $delete_pending;
 
@@ -1138,6 +1157,7 @@ sub load_myopac_update_email {
     my $e = $self->editor;
     my $ctx = $self->ctx;
     my $email = $self->cgi->param('email') || '';
+    my $current_pw = $self->cgi->param('current_pw') || '';
 
     # needed for most up-to-date email address
     if (my $r = $self->prepare_extended_user_info) { return $r };
@@ -1153,7 +1173,12 @@ sub load_myopac_update_email {
     my $stat = $U->simplereq(
         'open-ils.actor', 
         'open-ils.actor.user.email.update', 
-        $e->authtoken, $email);
+        $e->authtoken, $email, $current_pw);
+
+    if($U->event_equals($stat, 'INCORRECT_PASSWORD')) {
+        $ctx->{password_incorrect} = 1;
+        return Apache2::Const::OK;
+    }
 
     unless ($self->cgi->param("redirect_to")) {
         my $url = $self->apache->unparsed_uri;
@@ -1170,6 +1195,37 @@ sub load_myopac_update_username {
     my $e = $self->editor;
     my $ctx = $self->ctx;
     my $username = $self->cgi->param('username') || '';
+    my $current_pw = $self->cgi->param('current_pw') || '';
+
+    $self->prepare_extended_user_info;
+
+    my $allow_change = 1;
+    my $regex_check;
+    my $lock_usernames = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.lock_usernames');
+    if($lock_usernames == 1) {
+        # Policy says no username changes
+        $allow_change = 0;
+    } else {
+        # We want this further down.
+        $regex_check = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.barcode_regex');
+        my $username_unlimit = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.unlimit_usernames');
+        if($username_unlimit != 1) {
+            if(!$regex_check) {
+                # Default is "starts with a number"
+                $regex_check = '^\d+';
+            }
+            # You already have a username?
+            if($regex_check and $self->ctx->{user}->usrname !~ /$regex_check/) {
+                $allow_change = 0;
+            }
+        }
+    }
+    if(!$allow_change) {
+        my $url = $self->apache->unparsed_uri;
+        $url =~ s/update_username/prefs/;
+
+        return $self->generic_redirect($url);
+    }
 
     return Apache2::Const::OK 
         unless $self->cgi->request_method eq 'POST';
@@ -1179,12 +1235,30 @@ sub load_myopac_update_username {
         return Apache2::Const::OK;
     }
 
+    # New username can't look like a barcode if we have a barcode regex
+    if($regex_check and $username =~ /$regex_check/) {
+        $ctx->{invalid_username} = $username;
+        return Apache2::Const::OK;
+    }
+
+    # New username has to look like a username if we have a username regex
+    $regex_check = $ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.username_regex');
+    if($regex_check and $username !~ /$regex_check/) {
+        $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);
+            $e->authtoken, $username, $current_pw);
+
+        if($U->event_equals($evt, 'INCORRECT_PASSWORD')) {
+            $ctx->{password_incorrect} = 1;
+            return Apache2::Const::OK;
+        }
 
         if($U->event_equals($evt, 'USERNAME_EXISTS')) {
             $ctx->{username_exists} = $username;
@@ -1217,6 +1291,11 @@ sub load_myopac_update_password {
 
     my $pw_regex = $ctx->{get_org_setting}->($e->requestor->home_ou, 'global.password_regex');
 
+    if(!$pw_regex) {
+        # This regex duplicates the JSPac's default "digit, letter, and 7 characters" rule
+        $pw_regex = '(?=.*\d+.*)(?=.*[A-Za-z]+.*).{7,}';
+    }
+
     if($pw_regex and $new_pw !~ /$pw_regex/) {
         $ctx->{password_invalid} = 1;
         return Apache2::Const::OK;
index a80549f..b1ebf4a 100644 (file)
@@ -128,7 +128,7 @@ sub mylist_action_redirect {
         # on the results page, we want to redirect 
         # back to record that was affected
         $url = $self->ctx->{referer};
-        $url =~ s/#.*|$/#$anchor/g;
+        $url =~ s/#.*|$/#$anchor/;
     } 
 
     return $self->generic_redirect(
index 635678b..eb5d7ea 100644 (file)
@@ -168,7 +168,7 @@ sub load_rresults {
     }
 
     my $page = $cgi->param('page') || 0;
-    my $facet = $cgi->param('facet');
+    my @facets = $cgi->param('facet');
     my $limit = $self->_get_search_limit;
     my $loc = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
     my $offset = $page * $limit;
@@ -229,7 +229,7 @@ sub load_rresults {
         # 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
+        $query .= " $_" for @facets;
 
         $logger->activity("EGWeb: [search] $query");
 
@@ -263,6 +263,11 @@ sub load_rresults {
         }
     );
 
+    if ($page == 0) {
+        my $stat = $self->check_1hit_redirect($rec_ids);
+        return $stat if $stat;
+    }
+
     # shove recs into context in search results order
     for my $rec_id (@$rec_ids) {
         push(
@@ -276,6 +281,55 @@ sub load_rresults {
     return Apache2::Const::OK;
 }
 
+# If the calling search results in 1 record and the client
+# is configured to do so, redirect the search results to 
+# the record details page.
+sub check_1hit_redirect {
+    my ($self, $rec_ids) = @_;
+    my $ctx = $self->ctx;
+
+    return undef unless $rec_ids and @$rec_ids == 1;
+
+    my ($sname, $org);
+
+    if ($ctx->{is_staff}) {
+        $sname = 'opac.staff.jump_to_details_on_single_hit';
+        $org = $ctx->{user}->ws_ou;
+
+    } else {
+        $sname = 'opac.patron.jump_to_details_on_single_hit';
+        $org = ($ctx->{user}) ? 
+            $ctx->{user}->home_ou : 
+            $ctx->{orig_loc} || 
+            $self->ctx->{aou_tree}->()->id;
+    }
+
+    return undef unless 
+        $self->ctx->{get_org_setting}->($org, $sname);
+
+    my $base_url = sprintf(
+        '%s://%s%s/record/%s',
+        $ctx->{proto}, 
+        $self->apache->hostname,
+        $self->ctx->{opac_root},
+        $$rec_ids[0],
+    );
+    
+    # If we get here from the same record detail page to which we
+    # now wish to redirect, do not perform the redirect.  This
+    # approach seems to work well, with the rare exception of 
+    # performing a new serach directly from the detail page that 
+    # happens to result in the same single hit.  In this case, the 
+    # user will be left on the search results page.  This could be 
+    # overcome w/ additional CGI, etc., but I'm not sure it's necessary.
+    if (my $referer = $ctx->{referer}) {
+        $referer =~ s/([^?]*).*/$1/g;
+        return undef if $base_url eq $referer;
+    }
+
+    return $self->generic_redirect($base_url . '?' . $self->cgi->query_string);
+}
+
 # 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 {
@@ -385,13 +439,20 @@ sub marc_expert_search {
     }
 
     $self->ctx->{ids} = [ grep { $_ } @{$results->{ids}} ];
+    $self->ctx->{hit_count} = $results->{count};
+
+    return Apache2::Const::OK if @{$self->ctx->{ids}} == 0 or $args{internal};
+
+    if ($page == 0) {
+        my $stat = $self->check_1hit_redirect($self->ctx->{ids});
+        return $stat if $stat;
+    }
 
     my ($facets, @data) = $self->get_records_and_facets(
         $self->ctx->{ids}, undef, {flesh => "{holdings_xml,mra}"}
     );
 
     $self->ctx->{records} = [@data];
-    $self->ctx->{hit_count} = $results->{count};
 
     return Apache2::Const::OK;
 }
index 6af3a7a..3b72dbc 100644 (file)
@@ -201,13 +201,23 @@ sub get_records_and_facets {
     # gather up the unapi recs
     $ses->session_wait(1);
 
-    my $facets;
+    my $facets = {};
     if ($facet_key) {
-        $facets = $facet_req->gather(1);
-        $facets->{$_} = {
-            cmf => $self->ctx->{get_cmf}->($_),
-            data => $facets->{$_}
-        } for keys %$facets;    # quick-n-dirty
+        my $tmp_facets = $facet_req->gather(1);
+        for my $cmf_id (keys %$tmp_facets) {
+
+            # sort highest to lowest match count
+            my @entries;
+            my $entries = $tmp_facets->{$cmf_id};
+            for my $ent (keys %$entries) {
+                push(@entries, {value => $ent, count => $$entries{$ent}});
+            };
+            @entries = sort { $b->{count} <=> $a->{count} } @entries;
+            $facets->{$cmf_id} = {
+                cmf => $self->ctx->{get_cmf}->($cmf_id),
+                data => \@entries
+            }
+        }
     } else {
         $facets = undef;
     }
index d618782..29b067d 100755 (executable)
@@ -443,6 +443,7 @@ sub build_html {
        print $index <<"        HEADER";
 <html>
        <head>
+               <meta charset='utf-8'>
                <title>$$r{report}{name}</title>
                <style>
                        table { border-collapse: collapse; }
@@ -474,16 +475,16 @@ sub build_html {
        push @links, "<a class='dim' href='report-data.html.debug.html'>Debugging Info</a>";
 
        my $debug = new FileHandle (">$file.debug.html") or die "Cannot write to '$file.debug.html'";
-       print $debug "<html><head><title>DEBUG: $$r{report}{name}</title></head><body>";
+       print $debug "<html><head><meta charset='utf-8'><title>DEBUG: $$r{report}{name}</title></head><body>";
 
        {       no warnings;
-               print $debug '<h1>Generated SQL</h1><pre>' . $r->{resultset}->toSQL() . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>Template</h1><pre>' . Dumper( $r->{report}->{template} ) . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>Template Data</h1><pre>' . Dumper( OpenSRF::Utils::JSON->JSON2perl( $r->{report}->{template}->{data} ) ) . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>Report Parameter</h1><pre>' . Dumper( $r->{report} ) . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>Report Parameter Data</h1><pre>' . Dumper( OpenSRF::Utils::JSON->JSON2perl( $r->{report}->{data} ) ) . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>Report Run Time</h1><pre>' . $r->{resultset}->relative_time . "</pre><a href='$file'>Back to output index</a><hr/>";
-               print $debug '<h1>OpenILS::Reporter::SQLBuilder::ResultSet Object</h1><pre>' . Dumper( $r->{resultset} ) . "</pre><a href='$file'>Back to output index</a>";
+               print $debug '<h1>Generated SQL</h1><pre>' . $r->{resultset}->toSQL() . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>Template</h1><pre>' . Dumper( $r->{report}->{template} ) . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>Template Data</h1><pre>' . Dumper( OpenSRF::Utils::JSON->JSON2perl( $r->{report}->{template}->{data} ) ) . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>Report Parameter</h1><pre>' . Dumper( $r->{report} ) . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>Report Parameter Data</h1><pre>' . Dumper( OpenSRF::Utils::JSON->JSON2perl( $r->{report}->{data} ) ) . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>Report Run Time</h1><pre>' . $r->{resultset}->relative_time . "</pre><a href='report-data.html'>Back to output index</a><hr/>";
+               print $debug '<h1>OpenILS::Reporter::SQLBuilder::ResultSet Object</h1><pre>' . Dumper( $r->{resultset} ) . "</pre><a href='report-data.html'>Back to output index</a>";
        }
 
        print $debug '</body></html>';
@@ -496,7 +497,7 @@ sub build_html {
        if ($r->{html_format}) {
                # create the raw output html file
                my $raw = new FileHandle (">$file.raw.html") or die "Cannot write to '$file.raw.html'";
-               print $raw "<html><head><title>$$r{report}{name}</title>";
+               print $raw "<html><head><meta charset='utf-8'><title>$$r{report}{name}</title>";
 
                print $raw <<'          CSS';
                        <style>
index 2cbd5ef..ec8c655 100644 (file)
@@ -86,7 +86,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0630', :eg_version); -- tsbere/dbwells
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0637', :eg_version); -- miker/dkyle
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
index 5cf5f9b..6d82e73 100644 (file)
@@ -522,22 +522,24 @@ BEGIN
     END IF;
 
     -- Fail if the user has too many items with specific circ_modifiers checked out
-    FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
-        SELECT  INTO items_out COUNT(*)
-          FROM  action.circulation circ
-            JOIN asset.copy cp ON (cp.id = circ.target_copy)
-          WHERE circ.usr = match_user
-               AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
-            AND circ.checkin_time IS NULL
-            AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
-            AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
-        IF items_out >= out_by_circ_mod.items_out THEN
-            result.fail_part := 'config.circ_matrix_circ_mod_test';
-            result.success := FALSE;
-            done := TRUE;
-            RETURN NEXT result;
-        END IF;
-    END LOOP;
+    IF NOT renewal THEN
+        FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
+            SELECT  INTO items_out COUNT(*)
+              FROM  action.circulation circ
+                JOIN asset.copy cp ON (cp.id = circ.target_copy)
+              WHERE circ.usr = match_user
+                   AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
+                AND circ.checkin_time IS NULL
+                AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
+                AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
+            IF items_out >= out_by_circ_mod.items_out THEN
+                result.fail_part := 'config.circ_matrix_circ_mod_test';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END LOOP;
+    END IF;
 
     -- If we passed everything, return the successful matchpoint
     IF NOT done THEN
index ebea8cd..9f23e17 100644 (file)
@@ -51,6 +51,8 @@ DECLARE
 
     current_res         search.search_result%ROWTYPE;
     search_org_list     INT[];
+    luri_org_list       INT[];
+    tmp_int_list        INT[];
 
     check_limit         INT;
     core_limit          INT;
@@ -81,8 +83,19 @@ BEGIN
         ELSE
             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
         END IF;
+
+        SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
+
     ELSIF param_search_ou < 0 THEN
         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
+
+        FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
+            SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
+            luri_org_list := luri_org_list || tmp_int_list;
+        END LOOP;
+
+        SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
+
     ELSIF param_search_ou = 0 THEN
         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
     END IF;
@@ -146,7 +159,7 @@ BEGIN
                 AND uri.active
                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
                 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
-                AND cn.owning_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
           LIMIT 1;
 
         IF FOUND THEN
index 2dfc497..7f0263b 100644 (file)
@@ -1440,7 +1440,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 511, 'PERSISTENT_LOGIN', oils_i18n_gettext( 511,
     'Allows a user to authenticate and get a long-lived session (length configured in opensrf.xml)', 'ppl', 'description' )),
  ( 512, 'ACQ_INVOICE_REOPEN', oils_i18n_gettext( 512,
-    'Allows a user to reopen an Acquisitions invoice', 'ppl', 'description' ));
+    'Allows a user to reopen an Acquisitions invoice', 'ppl', 'description' )),
+ ( 513, 'DEBUG_CLIENT', oils_i18n_gettext( 513,
+    'Allows a user to use debug functions in the staff client', 'ppl', 'description' ));
 
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
 
@@ -2722,6 +2724,33 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'bool', null)
 
+,( 'circ.grace.extend', 'circ',
+    oils_i18n_gettext('circ.grace.extend',
+        'Auto-Extend Grace Periods',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend',
+        'When enabled grace periods will auto-extend. By default this will be only when they are a full day or more and end on a closed date, though other options can alter this.',
+        'coust', 'description'),
+    'bool', null)
+
+,( 'circ.grace.extend.all', 'circ',
+    oils_i18n_gettext('circ.grace.extend.all',
+        'Auto-Extending Grace Periods extend for all closed dates',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend.all',
+        'If enabled and Grace Periods auto-extending is turned on grace periods will extend past all closed dates they intersect, within hard-coded limits. This basically becomes "grace periods can only be consumed by closed dates".',
+        'coust', 'description'),
+    'bool', null)
+
+,( 'circ.grace.extend.into_closed', 'circ',
+    oils_i18n_gettext('circ.grace.extend.into_closed',
+        'Auto-Extending Grace Periods include trailing closed dates',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend.into_closed',
+         'If enabled and Grace Periods auto-extending is turned on grace periods will include closed dates that directly follow the last day of the grace period, to allow a backdate into the closed dates to assume "returned after hours on the last day of the grace period, and thus still within it" automatically.',
+        'coust', 'description'),
+    'bool', null)
+
 ,( 'circ.hold_boundary.hard', 'holds',
     oils_i18n_gettext('circ.hold_boundary.hard',
         'Hard boundary',
@@ -3653,6 +3682,15 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'bool', null)
 
+,( 'opac.lock_usernames', 'glob',
+    oils_i18n_gettext('opac.lock_usernames',
+        'Lock Usernames',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.lock_usernames',
+        'If enabled username changing via the OPAC will be disabled',
+        'coust', 'description'),
+    'bool', null)
+
 ,( 'opac.org_unit_hiding.depth', 'opac',
     oils_i18n_gettext('opac.org_unit_hiding.depth',
         'Org Unit Hiding Depth',
@@ -3671,6 +3709,24 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'interval', null)
 
+,( 'opac.unlimit_usernames', 'glob',
+    oils_i18n_gettext('opac.unlimit_usernames',
+        'Allow multiple username changes',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.unlimit_usernames',
+        'If enabled (and Lock Usernames is not set) patrons will be allowed to change their username when it does not look like a barcode. Otherwise username changing in the OPAC will only be allowed when the patron''s username looks like a barcode.',
+        'coust', 'description'),
+    'bool', null)
+
+,( 'opac.username_regex', 'glob',
+    oils_i18n_gettext('opac.username_regex',
+        'Patron username format',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.username_regex',
+        'Regular expression defining the patron username format, used for patron registration and self-service username changing only',
+        'coust', 'description'),
+    'string', null)
+
 ,( 'org.bounced_emails', 'prog',
     oils_i18n_gettext('org.bounced_emails',
         'Sending email address for patron notices',
@@ -3707,6 +3763,17 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'bool', null)
 
+,( 'print.custom_js_file', 'circ',
+    oils_i18n_gettext('print.custom_js_file',
+        'Printing: Custom Javascript File',
+        'coust', 'label'),
+    oils_i18n_gettext('print.custom_js_file',
+        'Full URL path to a Javascript File to be loaded when printing. Should'
+        || ' implement a print_custom function for DOM manipulation. Can change'
+        || ' the value of the do_print variable to false to cancel printing.',
+        'coust', 'description'),
+    'string', null)
+
 ,( 'serial.prev_issuance_copy_location', 'serial',
     oils_i18n_gettext('serial.prev_issuance_copy_location',
         'Previous Issuance Copy Location',
@@ -4390,6 +4457,23 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'integer', null)
 
+,( 'opac.staff.jump_to_details_on_single_hit', 'opac',
+    oils_i18n_gettext('opac.staff.jump_to_details_on_single_hit',
+        'Jump to details on 1 hit (staff client)',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.staff.jump_to_details_on_single_hit',
+        'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the OPAC within the staff client',
+        'coust', 'description'),
+    'bool', null)
+,( 'opac.patron.jump_to_details_on_single_hit', 'opac',
+    oils_i18n_gettext('opac.patron.jump_to_details_on_single_hit',
+        'Jump to details on 1 hit (public)',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.patron.jump_to_details_on_single_hit',
+        'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the public OPAC',
+        'coust', 'description'),
+    'bool', null)
+
 ;
 
 UPDATE config.org_unit_setting_type
@@ -4418,6 +4502,7 @@ INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
     ,(1, 'cat.label.font.family', '"monospace"')
     ,(1, 'cat.label.font.size', 10)
     ,(1, 'cat.label.font.weight', '"normal"')
+    ,(1, 'circ.grace.extend', 'true')
 ;
 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/0631.schema.located_uri_visiblity_fix.sql b/Open-ILS/src/sql/Pg/upgrade/0631.schema.located_uri_visiblity_fix.sql
new file mode 100644 (file)
index 0000000..b5843db
--- /dev/null
@@ -0,0 +1,326 @@
+-- Evergreen DB patch 0631.schema.located_uri_visiblity_fix.sql
+--
+BEGIN;
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0631', :eg_version);
+
+CREATE OR REPLACE FUNCTION search.query_parser_fts (
+
+    param_search_ou INT,
+    param_depth     INT,
+    param_query     TEXT,
+    param_statuses  INT[],
+    param_locations INT[],
+    param_offset    INT,
+    param_check     INT,
+    param_limit     INT,
+    metarecord      BOOL,
+    staff           BOOL
+) RETURNS SETOF search.search_result AS $func$
+DECLARE
+
+    current_res         search.search_result%ROWTYPE;
+    search_org_list     INT[];
+    luri_org_list       INT[];
+    tmp_int_list        INT[];
+
+    check_limit         INT;
+    core_limit          INT;
+    core_offset         INT;
+    tmp_int             INT;
+
+    core_result         RECORD;
+    core_cursor         REFCURSOR;
+    core_rel_query      TEXT;
+
+    total_count         INT := 0;
+    check_count         INT := 0;
+    deleted_count       INT := 0;
+    visible_count       INT := 0;
+    excluded_count      INT := 0;
+
+BEGIN
+
+    check_limit := COALESCE( param_check, 1000 );
+    core_limit  := COALESCE( param_limit, 25000 );
+    core_offset := COALESCE( param_offset, 0 );
+
+    -- core_skip_chk := COALESCE( param_skip_chk, 1 );
+
+    IF param_search_ou > 0 THEN
+        IF param_depth IS NOT NULL THEN
+            SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
+        ELSE
+            SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
+        END IF;
+
+        SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
+
+    ELSIF param_search_ou < 0 THEN
+        SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
+
+        FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
+            SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
+            luri_org_list := luri_org_list || tmp_int_list;
+        END LOOP;
+
+        SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
+
+    ELSIF param_search_ou = 0 THEN
+        -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
+    END IF;
+
+    OPEN core_cursor FOR EXECUTE param_query;
+
+    LOOP
+
+        FETCH core_cursor INTO core_result;
+        EXIT WHEN NOT FOUND;
+        EXIT WHEN total_count >= core_limit;
+
+        total_count := total_count + 1;
+
+        CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
+
+        check_count := check_count + 1;
+
+        PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
+        IF NOT FOUND THEN
+            -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
+            deleted_count := deleted_count + 1;
+            CONTINUE;
+        END IF;
+
+        PERFORM 1
+          FROM  biblio.record_entry b
+                JOIN config.bib_source s ON (b.source = s.id)
+          WHERE s.transcendant
+                AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
+
+        IF FOUND THEN
+            -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
+            visible_count := visible_count + 1;
+
+            current_res.id = core_result.id;
+            current_res.rel = core_result.rel;
+
+            tmp_int := 1;
+            IF metarecord THEN
+                SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+            END IF;
+
+            IF tmp_int = 1 THEN
+                current_res.record = core_result.records[1];
+            ELSE
+                current_res.record = NULL;
+            END IF;
+
+            RETURN NEXT current_res;
+
+            CONTINUE;
+        END IF;
+
+        PERFORM 1
+          FROM  asset.call_number cn
+                JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
+                JOIN asset.uri uri ON (map.uri = uri.id)
+          WHERE NOT cn.deleted
+                AND cn.label = '##URI##'
+                AND uri.active
+                AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
+                AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
+          LIMIT 1;
+
+        IF FOUND THEN
+            -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
+            visible_count := visible_count + 1;
+
+            current_res.id = core_result.id;
+            current_res.rel = core_result.rel;
+
+            tmp_int := 1;
+            IF metarecord THEN
+                SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+            END IF;
+
+            IF tmp_int = 1 THEN
+                current_res.record = core_result.records[1];
+            ELSE
+                current_res.record = NULL;
+            END IF;
+
+            RETURN NEXT current_res;
+
+            CONTINUE;
+        END IF;
+
+        IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
+
+            PERFORM 1
+              FROM  asset.call_number cn
+                    JOIN asset.copy cp ON (cp.call_number = cn.id)
+              WHERE NOT cn.deleted
+                    AND NOT cp.deleted
+                    AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
+                    AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                    AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+              LIMIT 1;
+
+            IF NOT FOUND THEN
+                PERFORM 1
+                  FROM  biblio.peer_bib_copy_map pr
+                        JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                  WHERE NOT cp.deleted
+                        AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
+                        AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+                -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
+                    excluded_count := excluded_count + 1;
+                    CONTINUE;
+                END IF;
+            END IF;
+
+        END IF;
+
+        IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
+
+            PERFORM 1
+              FROM  asset.call_number cn
+                    JOIN asset.copy cp ON (cp.call_number = cn.id)
+              WHERE NOT cn.deleted
+                    AND NOT cp.deleted
+                    AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
+                    AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                    AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+              LIMIT 1;
+
+            IF NOT FOUND THEN
+                PERFORM 1
+                  FROM  biblio.peer_bib_copy_map pr
+                        JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                  WHERE NOT cp.deleted
+                        AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
+                        AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+                    -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
+                    excluded_count := excluded_count + 1;
+                    CONTINUE;
+                END IF;
+            END IF;
+
+        END IF;
+
+        IF staff IS NULL OR NOT staff THEN
+
+            PERFORM 1
+              FROM  asset.opac_visible_copies
+              WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                    AND record IN ( SELECT * FROM unnest( core_result.records ) )
+              LIMIT 1;
+
+            IF NOT FOUND THEN
+                PERFORM 1
+                  FROM  biblio.peer_bib_copy_map pr
+                        JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
+                  WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                        AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+
+                    -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
+                    excluded_count := excluded_count + 1;
+                    CONTINUE;
+                END IF;
+            END IF;
+
+        ELSE
+
+            PERFORM 1
+              FROM  asset.call_number cn
+                    JOIN asset.copy cp ON (cp.call_number = cn.id)
+              WHERE NOT cn.deleted
+                    AND NOT cp.deleted
+                    AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                    AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+              LIMIT 1;
+
+            IF NOT FOUND THEN
+
+                PERFORM 1
+                  FROM  biblio.peer_bib_copy_map pr
+                        JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                  WHERE NOT cp.deleted
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                        AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+
+                    PERFORM 1
+                      FROM  asset.call_number cn
+                            JOIN asset.copy cp ON (cp.call_number = cn.id)
+                      WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                            AND NOT cp.deleted
+                      LIMIT 1;
+
+                    IF FOUND THEN
+                        -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
+                        excluded_count := excluded_count + 1;
+                        CONTINUE;
+                    END IF;
+                END IF;
+
+            END IF;
+
+        END IF;
+
+        visible_count := visible_count + 1;
+
+        current_res.id = core_result.id;
+        current_res.rel = core_result.rel;
+
+        tmp_int := 1;
+        IF metarecord THEN
+            SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+        END IF;
+
+        IF tmp_int = 1 THEN
+            current_res.record = core_result.records[1];
+        ELSE
+            current_res.record = NULL;
+        END IF;
+
+        RETURN NEXT current_res;
+
+        IF visible_count % 1000 = 0 THEN
+            -- RAISE NOTICE ' % visible so far ... ', visible_count;
+        END IF;
+
+    END LOOP;
+
+    current_res.id = NULL;
+    current_res.rel = NULL;
+    current_res.record = NULL;
+    current_res.total = total_count;
+    current_res.checked = check_count;
+    current_res.deleted = deleted_count;
+    current_res.visible = visible_count;
+    current_res.excluded = excluded_count;
+
+    CLOSE core_cursor;
+
+    RETURN NEXT current_res;
+
+END;
+$func$ LANGUAGE PLPGSQL;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0632.data.username-limit-settings.sql b/Open-ILS/src/sql/Pg/upgrade/0632.data.username-limit-settings.sql
new file mode 100644 (file)
index 0000000..62f70d8
--- /dev/null
@@ -0,0 +1,33 @@
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0632', :eg_version);
+
+INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
+( 'opac.username_regex', 'glob',
+    oils_i18n_gettext('opac.username_regex',
+        'Patron username format',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.username_regex',
+        'Regular expression defining the patron username format, used for patron registration and self-service username changing only',
+        'coust', 'description'),
+    'string')
+,( 'opac.lock_usernames', 'glob',
+    oils_i18n_gettext('opac.lock_usernames',
+        'Lock Usernames',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.lock_usernames',
+        'If enabled username changing via the OPAC will be disabled',
+        'coust', 'description'),
+    'bool')
+,( 'opac.unlimit_usernames', 'glob',
+    oils_i18n_gettext('opac.unlimit_usernames',
+        'Allow multiple username changes',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.unlimit_usernames',
+        'If enabled (and Lock Usernames is not set) patrons will be allowed to change their username when it does not look like a barcode. Otherwise username changing in the OPAC will only be allowed when the patron''s username looks like a barcode.',
+        'coust', 'description'),
+    'bool')
+;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0633.new_print_method.sql b/Open-ILS/src/sql/Pg/upgrade/0633.new_print_method.sql
new file mode 100644 (file)
index 0000000..9298aaf
--- /dev/null
@@ -0,0 +1,27 @@
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0633', :eg_version);
+
+INSERT into config.org_unit_setting_type
+( name, grp, label, description, datatype ) VALUES
+(
+        'print.custom_js_file', 'circ',
+        oils_i18n_gettext(
+            'print.custom_js_file',
+            'Printing: Custom Javascript File',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'print.custom_js_file',
+            'Full URL path to a Javascript File to be loaded when printing. Should'
+            || ' implement a print_custom function for DOM manipulation. Can change'
+            || ' the value of the do_print variable to false to cancel printing.',
+            'coust',
+            'description'
+        ),
+        'string'
+    );
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0634.security_lockdown.sql b/Open-ILS/src/sql/Pg/upgrade/0634.security_lockdown.sql
new file mode 100644 (file)
index 0000000..c6b9a8c
--- /dev/null
@@ -0,0 +1,10 @@
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0634', :eg_version);
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+ ( 513, 'DEBUG_CLIENT', oils_i18n_gettext( 513,
+    'Allows a user to use debug functions in the staff client', 'ppl', 'description' ));
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0635.data.opac.jump-to-details-setting.sql b/Open-ILS/src/sql/Pg/upgrade/0635.data.opac.jump-to-details-setting.sql
new file mode 100644 (file)
index 0000000..c92be57
--- /dev/null
@@ -0,0 +1,44 @@
+-- Evergreen DB patch 0635.data.opac.jump-to-details-setting.sql
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0635', :eg_version);
+
+INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype )
+    VALUES (
+        'opac.staff.jump_to_details_on_single_hit', 
+        'opac',
+        oils_i18n_gettext(
+            'opac.staff.jump_to_details_on_single_hit',
+            'Jump to details on 1 hit (staff client)',
+            'coust', 
+            'label'
+        ),
+        oils_i18n_gettext(
+            'opac.staff.jump_to_details_on_single_hit',
+            'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the OPAC within the staff client',
+            'coust', 
+            'description'
+        ),
+        'bool'
+    ), (
+        'opac.patron.jump_to_details_on_single_hit', 
+        'opac',
+        oils_i18n_gettext(
+            'opac.patron.jump_to_details_on_single_hit',
+            'Jump to details on 1 hit (public)',
+            'coust', 
+            'label'
+        ),
+        oils_i18n_gettext(
+            'opac.patron.jump_to_details_on_single_hit',
+            'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the public OPAC',
+            'coust', 
+            'description'
+        ),
+        'bool'
+    );
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0636.data.grace_period_extend.sql b/Open-ILS/src/sql/Pg/upgrade/0636.data.grace_period_extend.sql
new file mode 100644 (file)
index 0000000..97757e5
--- /dev/null
@@ -0,0 +1,52 @@
+-- Evergreen DB patch 0636.data.grace_period_extend.sql
+--
+-- OU setting turns on grace period auto extension. By default they only do so
+-- when the grace period ends on a closed date, but there are two modifiers to
+-- change that.
+-- 
+-- The first modifier causes grace periods to extend for all closed dates that
+-- they intersect. This is "grace periods are only consumed by open days."
+-- 
+-- The second modifier causes a grace period that ends just before a closed
+-- day, with or without extension having happened, to include the closed day
+-- (and any following it) as well. This is mainly so that a backdate into the
+-- closed period following the grace period will assume the "best case" of the
+-- item having been returned after hours on the last day of the closed date.
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0636', :eg_version);
+
+INSERT INTO config.org_unit_setting_type(name, grp, label, description, datatype) VALUES
+
+( 'circ.grace.extend', 'circ',
+    oils_i18n_gettext('circ.grace.extend',
+        'Auto-Extend Grace Periods',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend',
+        'When enabled grace periods will auto-extend. By default this will be only when they are a full day or more and end on a closed date, though other options can alter this.',
+        'coust', 'description'),
+    'bool', null)
+
+,( 'circ.grace.extend.all', 'circ',
+    oils_i18n_gettext('circ.grace.extend.all',
+        'Auto-Extending Grace Periods extend for all closed dates',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend.all',
+        'If enabled and Grace Periods auto-extending is turned on grace periods will extend past all closed dates they intersect, within hard-coded limits. This basically becomes "grace periods can only be consumed by closed dates".',
+        'coust', 'description'),
+    'bool', null)
+
+,( 'circ.grace.extend.into_closed', 'circ',
+    oils_i18n_gettext('circ.grace.extend.into_closed',
+        'Auto-Extending Grace Periods include trailing closed dates',
+        'coust', 'label'),
+    oils_i18n_gettext('circ.grace.extend.into_closed',
+         'If enabled and Grace Periods auto-extending is turned on grace periods will include closed dates that directly follow the last day of the grace period, to allow a backdate into the closed dates to assume "returned after hours on the last day of the grace period, and thus still within it" automatically.',
+        'coust', 'description'),
+    'bool', null);
+
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0637.schema.renewal_checkout_counting.sql b/Open-ILS/src/sql/Pg/upgrade/0637.schema.renewal_checkout_counting.sql
new file mode 100644 (file)
index 0000000..e1f385a
--- /dev/null
@@ -0,0 +1,201 @@
+-- Patch from Doug Kyle re: https://bugs.launchpad.net/evergreen/+bug/822918
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('0636', :eg_version);
+
+CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
+DECLARE
+    user_object             actor.usr%ROWTYPE;
+    standing_penalty        config.standing_penalty%ROWTYPE;
+    item_object             asset.copy%ROWTYPE;
+    item_status_object      config.copy_status%ROWTYPE;
+    item_location_object    asset.copy_location%ROWTYPE;
+    result                  action.circ_matrix_test_result;
+    circ_test               action.found_circ_matrix_matchpoint;
+    circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
+    out_by_circ_mod         config.circ_matrix_circ_mod_test%ROWTYPE;
+    circ_mod_map            config.circ_matrix_circ_mod_test_map%ROWTYPE;
+    hold_ratio              action.hold_stats%ROWTYPE;
+    penalty_type            TEXT;
+    items_out               INT;
+    context_org_list        INT[];
+    done                    BOOL := FALSE;
+BEGIN
+    -- Assume success unless we hit a failure condition
+    result.success := TRUE;
+
+    -- Need user info to look up matchpoints
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
+
+    -- (Insta)Fail if we couldn't find the user
+    IF user_object.id IS NULL THEN
+        result.fail_part := 'no_user';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Need item info to look up matchpoints
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
+
+    -- (Insta)Fail if we couldn't find the item 
+    IF item_object.id IS NULL THEN
+        result.fail_part := 'no_item';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
+
+    circ_matchpoint             := circ_test.matchpoint;
+    result.matchpoint           := circ_matchpoint.id;
+    result.circulate            := circ_matchpoint.circulate;
+    result.duration_rule        := circ_matchpoint.duration_rule;
+    result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
+    result.max_fine_rule        := circ_matchpoint.max_fine_rule;
+    result.hard_due_date        := circ_matchpoint.hard_due_date;
+    result.renewals             := circ_matchpoint.renewals;
+    result.grace_period         := circ_matchpoint.grace_period;
+    result.buildrows            := circ_test.buildrows;
+
+    -- (Insta)Fail if we couldn't find a matchpoint
+    IF circ_test.success = false THEN
+        result.fail_part := 'no_matchpoint';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- All failures before this point are non-recoverable
+    -- Below this point are possibly overridable failures
+
+    -- Fail if the user is barred
+    IF user_object.barred IS TRUE THEN
+        result.fail_part := 'actor.usr.barred';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    -- Fail if the item can't circulate
+    IF item_object.circulate IS FALSE THEN
+        result.fail_part := 'asset.copy.circulate';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    -- Fail if the item isn't in a circulateable status on a non-renewal
+    IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
+        result.fail_part := 'asset.copy.status';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    -- Alternately, fail if the item isn't checked out on a renewal
+    ELSIF renewal AND item_object.status <> 1 THEN
+        result.fail_part := 'asset.copy.status';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    -- Fail if the item can't circulate because of the shelving location
+    SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
+    IF item_location_object.circulate IS FALSE THEN
+        result.fail_part := 'asset.copy_location.circulate';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    -- Use Circ OU for penalties and such
+    SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_ou );
+
+    IF renewal THEN
+        penalty_type = '%RENEW%';
+    ELSE
+        penalty_type = '%CIRC%';
+    END IF;
+
+    FOR standing_penalty IN
+        SELECT  DISTINCT csp.*
+          FROM  actor.usr_standing_penalty usp
+                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+          WHERE usr = match_user
+                AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
+                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                AND csp.block_list LIKE penalty_type LOOP
+
+        result.fail_part := standing_penalty.name;
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END LOOP;
+
+    -- Fail if the test is set to hard non-circulating
+    IF circ_matchpoint.circulate IS FALSE THEN
+        result.fail_part := 'config.circ_matrix_test.circulate';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    -- Fail if the total copy-hold ratio is too low
+    IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
+        SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
+        IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
+            result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+
+    -- Fail if the available copy-hold ratio is too low
+    IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
+        IF hold_ratio.hold_count IS NULL THEN
+            SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
+        END IF;
+        IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
+            result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+
+    -- Fail if the user has too many items with specific circ_modifiers checked out
+    IF NOT renewal THEN
+        FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
+            SELECT  INTO items_out COUNT(*)
+              FROM  action.circulation circ
+                JOIN asset.copy cp ON (cp.id = circ.target_copy)
+              WHERE circ.usr = match_user
+                   AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
+                AND circ.checkin_time IS NULL
+                AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
+                AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
+            IF items_out >= out_by_circ_mod.items_out THEN
+                result.fail_part := 'config.circ_matrix_circ_mod_test';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END LOOP;
+    END IF;
+
+    -- If we passed everything, return the successful matchpoint
+    IF NOT done THEN
+        RETURN NEXT result;
+    END IF;
+
+    RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+
index 12e9acb..9707c74 100644 (file)
@@ -1,7 +1,8 @@
 [% WRAPPER base.tt2 %]
 [% ctx.page_title = 'EDI Accounts' %]
 <style type="text/css">
-    .footer_notes { padding-top: 16px; font-size: smaller; }
+    #pListGrid { min-height: 60px; height: 100%; padding-bottom: 5px; }
+    .footer_notes { font-size: smaller; }
 </style>
 
 <div id='main-list-div'>
index 58f81ed..cf831e1 100644 (file)
@@ -1,5 +1,5 @@
 [% WRAPPER base.tt2 %]
-<h1>Code Value Maps</h1> <br/>
+<h1>Coded Value Maps</h1> <br/>
 
 <div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
     <div>Coded Value Maps</div>
index 04e2041..217a069 100644 (file)
@@ -4,7 +4,6 @@
     ctx.page_title = l("Advanced Search");
     pane = CGI.param("pane") || "advanced" %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         <div id="adv_search_parent">
             <div id="adv_search_tabs">
                 <a href="?pane=advanced" [% IF pane == 'advanced' %]class="on" [% END %]id="adv_search">[% l('Advanced Search') %]</a>
index 6e0ad0b..a290b64 100644 (file)
@@ -7,7 +7,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Call Number Browse"); %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index fa64e65..19dfc58 100644 (file)
@@ -3,7 +3,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Home") %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index 4be13bb..cbdacaa 100644 (file)
@@ -4,7 +4,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Account Login") %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index ac24efd..b77eeae 100644 (file)
@@ -4,7 +4,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Record Detail") %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index 2aeea79..87cdfd0 100644 (file)
                     </div>[% l("Username") %]
                 </td>
                 <td class='light_border'>[% ctx.user.usrname | html %]</td>
+                [% IF ctx.username_change_disallowed %]
+                <td></td>
+                [% ELSE %]
                 <td class='light_border'><a href='update_username'>[% l("Change") %]</a></td>
+                [% END %]
             </tr>
             <tr>
                 <td class='color_4 light_border'>[% l("Password") %]</td>
index b920e08..6b662bd 100644 (file)
@@ -9,6 +9,12 @@
         [% bad_email = ctx.invalid_email | html %]
         [% l('The email address "<b>[_1]</b>" is invalid.  Please try a different email address.', bad_email) %]
     </div>
+
+[% ELSIF ctx.password_incorrect %]
+    <div id='account-update-email-error'>
+        [% |l %] Your current password was not correct. [% END %]
+    </div>
+
 [% END %]
 
 <form method='POST' id='account-update-email'>
@@ -17,6 +23,7 @@
     [% END %]
     <table> 
         <tr><td>[% l('Current Email') %]</td><td>[% ctx.user.email | html %]</td></tr>
+        <tr><td>[% l('Current Password') %]</td><td><input type='password' name='current_pw'/></td></tr>
         <tr><td>[% l('New Email') %]</td><td><input type='text' name='email' value='[% ctx.invalid_email | html %]'/></td></tr>
         <tr><td colspan='2' align='center'><input value="[% l('Submit') %]" type='submit'/></td></tr>
     </table>
index 6f48320..a3a0bd2 100644 (file)
@@ -7,7 +7,7 @@
 [% IF ctx.invalid_username %]
     <div id='account-update-email-error'> <!-- borrow css from update-email page -->
         [% bad_user = ctx.invalid_username | html %]
-        [% l('"<b>[_1]</b>" is not a valid username.  Usernames cannot have any spaces.  Please try a different username.', bad_user) %]
+        [% l('"<b>[_1]</b>" is not a valid username.  Usernames cannot have any spaces or look like a barcode, and may be restricted by policy.  Please try a different username.', bad_user) %]
     </div>
 
 [% ELSIF ctx.username_exists %]
         The username "<b>[_1]</b>" is taken.  Please try a different username.
         [% END %]
     </div>
+
+[% ELSIF ctx.password_incorrect %]
+    <div id='account-update-email-error'>
+        [% |l %] Your current password was not correct. [% END %]
+    </div>
+
 [% END %]
 
 <form method='POST' id='account-update-email'> 
     <table> 
         <tr><td>[% l('Current Username') %]</td><td>[% ctx.user.usrname | html %]</td></tr>
+        <tr><td>[% l('Current Password') %]</td><td><input type='password' name='current_pw'/></td></tr>
         <tr><td>[% l('New Username') %]</td><td><input type='text' name='username' value='[% ctx.invalid_username | html %]'/></td></tr>
         <tr><td colspan='2' align='center'><input value="[% l('Submit') %]" type='submit'/></td></tr>
     </table>
index 1598b69..66ecea4 100644 (file)
@@ -9,6 +9,19 @@
     queries = CGI.param('query');
     bools = CGI.param('bool') || ['and' x 3];
     qtypes = CGI.param('qtype') || ['keyword' x 3];
+
+    IF !qtypes.0; # not an array
+        qtypes = [qtypes];
+        bools = [bools];
+        queries = [queries];
+    END;
+
+    WHILE qtypes.size < 3;
+        qtypes.push('keyword');
+        bools.push('and');
+        queries.push('');
+    END;
+
     FOR qtype IN qtypes;
         c = contains.shift;
         b = bools.shift;
index c66c74f..0a6c10c 100644 (file)
@@ -9,6 +9,7 @@
         <link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/default/opac/style.css" />
         <title>[% l('Catalog - [_1]', ctx.page_title) %]</title>
         <link rel="unapi-server" type="application/xml" title="unAPI" href="/opac/extras/unapi" />
+        [% INCLUDE 'opac/parts/goog_analytics.tt2' %]
     </head>
     <body>
         [% content %] 
index 0443b97..ce60628 100644 (file)
@@ -2,6 +2,7 @@
     USE CGI = CGI_utf8;
     hostname = CGI.url({'-base' => 1});
 -%]
+<div id="footer-wrap">
 <div id="footer">
     <a href="[% hostname %]">[% l('Dynamic catalog') %]</a> &nbsp;|&nbsp;
     <a href="http://example.com">[% l('Bottom Link 2') %]</a> &nbsp;|&nbsp;
     <div id="footer_logo">
         [% l('Powered by') %]
         <a href="http://evergreen-ils.org">
-            <img src="[% ctx.media_prefix %]/opac/images/eg_tiny_logo.jpg"
+            <img src="[% ctx.media_prefix %]/opac/images/eg_tiny_logo.png"
                 style="border:none; width: 94px; height: 16px;"
                 alt="[% l('Evergreen') %]"
             />
         </a>
     </div>
 </div>
-
+</div>
diff --git a/Open-ILS/src/templates/opac/parts/goog_analytics.tt2 b/Open-ILS/src/templates/opac/parts/goog_analytics.tt2
new file mode 100644 (file)
index 0000000..df69b43
--- /dev/null
@@ -0,0 +1,18 @@
+[%- PROCESS "opac/parts/config.tt2";
+    IF google_analytics.enabled == 'true' %]
+
+<!-- http://www.google.com/support/googleanalytics/bin/answer.py?answer=174090 -->
+<script type="text/javascript">
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', '[% google_analytics.code %]']);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+        var ga = document.createElement('script'); 
+        ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; 
+        s.parentNode.insertBefore(ga, s);
+    })();
+</script>
+[%- END %]
index e5104b6..4ed9017 100644 (file)
@@ -1,3 +1,3 @@
 <div style='width:664px;height:35px;background:#FFFFFF;'>
-    <strong><center><img src="[% ctx.media_prefix %]/opac/images/main_logo.jpg" /></center></strong>
+    <strong><center><img src="[% ctx.media_prefix %]/opac/images/main_logo.png" /></center></strong>
 </div>
index 9dbbeac..35ff2bb 100644 (file)
     IF CGI.https; url = url.replace('^http:', 'https:'); END; %]
 <script type='text/javascript' id='EIT' src='[% url %]'></script>
 [%- END %]
-
-[%- IF google_analytics.enabled == 'true' %]
-<!-- Google Analytics -->
-<script type="text/javascript">
-/* uncomment when ready */ /*
-  var _gaq = _gaq || [];
-  _gaq.push(['_setAccount', '[% google_analytics.code %]']);
-  _gaq.push(['_trackPageview']);
-
-  (function() {
-    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
-    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-  })();
-*/
-</script>
-<!-- End Google Analytics -->
-[%- END %]
index 62291cf..a65cf2a 100644 (file)
@@ -11,7 +11,6 @@
 %]
     [% INCLUDE "opac/parts/topnav.tt2" %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index 0fabf46..fc06cac 100644 (file)
@@ -1,21 +1,21 @@
-[%  
+[%-  
 
 authors = [
     {
         type => 'author', 
-        label => l('Authors: '),
+        label => l('Author'),
         xpath => '//*[@tag="100"]|//*[@tag="110"]|//*[@tag="111"]'
     }, {
         type => 'added', 
-        label => l('Added Authors: '),
+        label => l('Added Author'),
         xpath => '//*[@tag="700"]|//*[@tag="710"]|//*[@tag="711"]'
     }, {
         type => 'credits', 
-        label => l('Credits: '),
+        label => l('Credited'),
         xpath => '//*[@tag="100"]|//*[@tag="110"]|//*[@tag="111"]'
     }, {
         type => 'cast', 
-        label => l('Cast'),
+        label => l('Cast'),
         xpath => '//*[@tag="508"]'
     }, {
         type => 'notes', 
@@ -26,27 +26,32 @@ authors = [
 
 BLOCK build_author_links;
     FOR node IN ctx.marc_xml.findnodes(xpath);
+        term = '';
+        qterm = '';
         FOR subfield IN node.childNodes;
             NEXT UNLESS subfield.nodeName == "subfield";
             code = subfield.getAttribute('code');
             NEXT UNLESS code.match('[a-z]');
-            term = subfield.textContent | html;
-            url = mkurl(ctx.opac_root _ '/results', {query => subfield.textContent, qtype => 'author'}, ['page', 'expand']);
-            '<br/><a href="' _ url _ '">' _ term _ '</a>';
+            sf_raw = subfield.textContent;
+            sf = subfield.textContent | html;
+            term = term _ ' ' _ sf;
+            qterm = qterm _ ' ' _ sf_raw;
         END;
+        url = mkurl(ctx.opac_root _ '/results', {query => qterm, qtype => 'author'}, ['page', 'expand']);
+        author_type = label | html;
+        '<a href="' _ url _ '">' _ term _ '</a> (' _ author_type _ '). ';
     END;
 END;
 %]
 
-<div class='rdetail_extras_div'>
-[% FOREACH author IN authors;
+<div class='rdetail_authors_div'>
+[%- FOREACH author IN authors;
     NEXT UNLESS author.xpath; 
-    links = PROCESS build_author_links(xpath=author.xpath);
+    links = PROCESS build_author_links(xpath=author.xpath, label=author.label);
     IF links.match('\S') %]
-    <strong>[% author.label | html %]</strong>
-    <div class='rdetail-author-div'>[% links %]</div>
-    [% END %]
-[% END %]
+    <span class='rdetail-author-div'>[% links %]</span>
+    [%- END %]
+[%- END %]
 </div>
 
 
index bc42f5d..7010b46 100644 (file)
@@ -50,7 +50,5 @@
         [% l("This record has been deleted from the database.  We recommend that you remove this title from any bookbags it may have been added to.") %]
     </div>
     [% INCLUDE "opac/parts/record/summary.tt2" %]
-    <br />
-    [% INCLUDE "opac/parts/record/extras.tt2" %]
 </div>
 <!-- ****************** end; page_rdetail.xml ***************************** -->
index 5bf5716..4a473c3 100644 (file)
         END;
 
         extras = [
-            {name => 'subjects', label => l('Subject')}, 
             {name => 'summaryplus',  label => l('Summaries &amp; More'), hide => hide_summary}, 
             {name => 'contents',  label => l('Contents'), hide => !attrs.contents},
-            {name => 'authors',  label => l('Authors')}, 
-            {name => 'series',   label => l('Series')},
             {name => 'annotation', label => l('Annotation'), hide => 1}, 
             {name => 'awards',  label => l('Awards, Reviews, & Suggested Reads')}, 
             {name => 'excerpt',  label => l('Excerpt'), hide => 1},
index edf99ba..06def16 100644 (file)
@@ -3,14 +3,30 @@
     loc = CGI.param('loc');
 %]
 
-<div>
-    <table cellpadding="0" cellspacing="0" border="0">
-    [%  FOR tag IN series_tags; %]
-        <tr><td style='padding-top:5px;'>
-        [%  FOR node IN ctx.marc_xml.findnodes('//*[@tag="' _ tag _ '"]/*') %]
-            [% IF !loop.first %]<span>&mdash;</span> [% END %]
-            <a href="[% ctx.opac_root %]/results?qtype=series&amp;query=[% node.textContent | uri %]&amp;loc=[% loc %]">[% node.textContent | html %]</a>
-        [% END %]
-    [% END; %]
-    </table>
-</div>
+[% BLOCK render_series;
+    results = [];
+    FOR tag IN series_tags;
+        FOR node IN ctx.marc_xml.findnodes('//*[@tag="' _ tag _ '"]/*');
+            node_uri = node.textContent | uri;
+            node_html = node.textContent | html;
+            IF !loop.first;
+                results.last = result.last _ '<span>&mdash;</span>';
+            END;
+            results.push('<a href="' _ ctx.opac_root 
+                _ '/results?qtype=series&amp;query=' _ node_uri _ '&amp;loc='
+                _ loc _ '">' _ node_html _ '</a>'
+            );
+        END;
+    END; 
+END;
+%]
+
+[%- series_anchors = PROCESS render_series;
+    IF series_anchors.length > 0; %]
+<h2 class='rdetail_related_series'>[% l('Search for related items by series') %]</h2>
+<ul>
+    [%- FOR entry IN series_anchors %]
+    <li class='rdetail_series_value'>[% entry %]</li>
+    [% END %]
+</ul>
+[%- END %]
index 25f6e2e..34f80b1 100644 (file)
     END 
 %]
 
-<div>
-    <table cellpadding="0" cellspacing="0" border="0">
-    [%  any_subjects = 0;
-        FOREACH subj IN subjects;
-            content = PROCESS render_subject(xpath=subj.xpath);
-            IF content.match('\S');
-                any_subjects = 1; %]
-            <tr>
-                <td width="1" style="padding:5px 7px 0px 0px;" valign="top">
-                    <strong>[% subj.label %]</strong>
-                </td>
-                <td style="padding-top:5px;"><div>[% content %] </div></td>
-            </tr>
-            [% END; %]
-        [% END; %]
-    [% IF any_subjects == 0 %]
-        <tr><td><i>[% l('No Subjects') %]</i></td></tr>
-    [% END; %]
-    </table>
-</div>
+[%  BLOCK render_all_subjects;
+    FOREACH subj IN subjects;
+        content = PROCESS render_subject(xpath=subj.xpath);
+        IF content.match('\S');
+%]
+        <table class='rdetail_subject'>
+            <tbody>
+                <tr>
+                    <td class='rdetail_subject_type'>[% subj.label %]</td>
+                    <td class='rdetail_subject_value'>[% content %]</td>
+                </tr>
+            </tbody>
+        </table>
+        [%- END; %]
+    [%- END; %]
+[%- END %]
+
+[%-  subject_html = PROCESS render_all_subjects;
+    IF subject_html.length > 0;
+%]
+<h2 class='rdetail_related_subjects'>[% l('Search for related items by subject') %]</h2>
+[%- subject_html %]
+[%- END %]
index a44e447..490a860 100644 (file)
     [% INCLUDE 'opac/parts/record/refworks.tt2' %]
 [%- END %]
 
-<!-- This holds the record summary information -->
+<hr style="margin-top: 1em;" />
 
-<table width="100%" border="0" cellpadding="0" cellspacing="0" id="rdetail_details_table">
-    <tbody>
-        <tr>
-            <td width="90" valign="top" id="rdetail_image_cell">
-                [% ident = attrs.isbn_clean || attrs.upc; IF ident; %]
-                <a href='[% ctx.media_prefix %]/opac/extras/ac/jacket/large/[% ident | uri %]'><img
-                    alt="[% l('Image of item') %]" id='rdetail_image'
-                    src='[% ctx.media_prefix %]/opac/extras/ac/jacket/[% record.summary.jacket_size %]/[% ident | uri %]' /></a>
-                [% END %]
-                <br />
-            </td>
-    
-            <td valign="top">
-                <table border="0" cellpadding="0" cellspacing="0" width="100%">
-                    <tr>
-                        <td valign="top">
-                            <span id='rdetail_title'>[% attrs.title_extended | html %]</span><br />
-                            [% IF attrs.author %]
-                            <span class='opac-auto-030'>[% l("Author") %]:</span>
-                            <em><a title='[% l("Perform an author search") %]'
-                                    href="[%- 
-                                        authorquery = attrs.author | replace('[,\.:;]', '');
-                                        mkurl(ctx.opac_root _ '/results', {qtype => 'author', query => authorquery}, ['page'])
-                                        -%]">[% attrs.author | html %]</a></em>
-                            [% END %]
-                        </td>
-                        <td align="right" valign="top" nowrap="nowrap" style="white-space:nowrap;">
-                            <div style="width:280px;text-align:left;margin-top:3px;">
-                                <div style="float:right;">
-                                    <div class="rdetail_aux_utils opac-auto-010">
-                                        <a href="[% mkurl(ctx.opac_root _ '/place_hold', {hold_target => ctx.bre_id, hold_type => 'T'}) %]" 
-                                            class="no-dec"><img src="[% ctx.media_prefix %]/images/green_check.png" alt="[% l('place hold') %]" /><span 
-                                                    style="position:relative;top:-3px;left:3px;">[% l('Place Hold') %]</span></a>
-                                    </div>
-                                    <div class="rdetail_aux_utils opac-auto-121">
-                                        [%  
-                                            operation = ctx.mylist.grep(ctx.bre_id).size ? "delete" : "add";
-                                            label = (operation == "add") ? l("Add to my list") : l("Remove from my list"); 
-                                        %]
-                                        <a href="[% ctx.opac_root %]/mylist/[% operation %]?record=[% ctx.bre_id %]" class="no-dec">
-                                            <img src="[% ctx.media_prefix %]/images/clipboard.png" alt="" />
-                                            [% label %]
-                                        </a>
-                                    </div>
-                                </div>
-                                <div style="float:right;margin-right:17px;">
-                                    [% IF attrs.format_icon %]
-                                    <img alt="[% attrs.format_label %]" title="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" />
-                                    [% END %]
-                                </div>
-                            </div>
-                        </td>
-                    </tr>
-                </table>
-                <div class='opac-auto-018'>
-                    <table border="0" cellpadding="0" width="100%">
-                        <tr>
-                            <td nowrap='nowrap' valign="top">
-                                [% IF attrs.isbns.0 %]<strong>[% l("ISBN") %]</strong>[% END %]
-                            </td>
-                            <td valign="top">
-                                [% FOR isbn IN attrs.isbns %][% IF !loop.first; %]<br/>[% END; isbn | html ; END %]
-                            </td>
-                            <td nowrap='nowrap' valign="top">
-                                [% IF attrs.phys_desc %]<strong>[% l("Physical Description") %]</strong>[% END %]
-                            </td>
-                            <td valign="top">[% attrs.phys_desc | html %]</td>
-                        </tr>
-                        [%- IF openurl.enabled == 'true';
-                            FOR issn IN args.issns;
-                                sfx = ResolverResolver.resolve_issn(issn, openurl.baseurl);
-                                FOR res IN sfx;
-                        %]
-                            <tr name="results_issn_tr">
-                                <td valign="top">
-                                    <strong><a href="[% res.target_url %]">
-                                        [% res.public_name %]</a></strong>
-                                </td>
-                                <td>[% res.target_coverage %]</td>
-                            </tr>
-                                [% END %]
-                            [% END %]
-                        [% END %]
-                        <tr>
-                            <td nowrap='nowrap' valign="top">
-                                <strong>[% IF attrs.marc_cn; l("Call Number"); END %]</strong>
-                            </td>
-                            <td valign="top">[% attrs.marc_cn | html %]</td>
-                            <td nowrap='nowrap' valign="top">
-                                <strong>[% IF attrs.edition; l("Edition"); END %]</strong>
-                            </td>
-                            <td valign="top">[% attrs.edition | html %]</td>
-                        </tr>
-                        <tr>
-                            <td nowrap='nowrap' valign="top">
-                                <strong>[% IF attrs.publisher; l("Publisher"); END %]</strong>
-                            </td>
-                            <td valign="top">[% attrs.publisher | html %]</td>
-                            <td nowrap='nowrap' valign="top">
-                                <strong>[% IF attrs.pubdate; l("Publication Date"); END %]</strong>
-                            </td>
-                            <td valign="top">[% attrs.pubdate | html %]</td>
-                        </tr>
-                    </table>
+[%- # This holds the record summary information %]
+<div id="rdetail_image_div" style="float: left; margin-right: 1em;">
+    [% ident = attrs.isbn_clean || attrs.upc; IF ident; %]
+    <a href='[% ctx.media_prefix %]/opac/extras/ac/jacket/large/[% ident | uri %]'><img
+        alt="[% l('Image of item') %]" id='rdetail_image'
+        src='[% ctx.media_prefix %]/opac/extras/ac/jacket/[% record.summary.jacket_size %]/[% ident | uri %]' /></a>
+    [% END %]
+    <br />
+</div>
+<div id="rdetail_actions_div" style="float: right; margin-left: 1em;">
+    <div class="rdetail_aux_utils opac-auto-010">
+        <a href="[% mkurl(ctx.opac_root _ '/place_hold', {hold_target => ctx.bre_id, hold_type => 'T'}) %]" 
+        class="no-dec"><img src="[% ctx.media_prefix %]/images/green_check.png" alt="[% l('place hold') %]" /><span 
+        style="position:relative;top:-3px;left:3px;">[% l('Place Hold') %]</span></a>
+    </div>
+    <div class="rdetail_aux_utils opac-auto-121">
+    [%-  
+        operation = ctx.mylist.grep(ctx.bre_id).size ? "delete" : "add";
+        label = (operation == "add") ? l("Add to my list") : l("Remove from my list"); 
+    %]
+        <a href="[% ctx.opac_root %]/mylist/[% operation %]?record=[% ctx.bre_id %]" class="no-dec">
+            <img src="[% ctx.media_prefix %]/images/clipboard.png" alt="" />
+            [% label %]
+        </a>
+    </div>
+</div>
 
-                    <!-- hold/copy summary -->
-                    <div style="padding-top:15px;">
-                        <div>
-                            [% l("[quant,_1,Hold,Holds] with [quant,_2,total copy,total copies]", 
-                                ctx.record_hold_count, ctx.copy_summary.0.count) %]
-                        </div>
-                        <div>[% l('[quant,_1,Copy,Copies] available', ctx.copy_summary.0.available) %]</div>
-                    </div>
+<div id='rdetail_title_div'>
+    [%- IF attrs.format_icon %]
+    <div style="float:right;margin-right:17px;">
+        <img alt="[% attrs.format_label %]" title="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" />
+    </div>
+    [%- END %]
+    <h1 id='rdetail_title'>[% attrs.title_extended | html %]</h1>
+    [%- INCLUDE "opac/parts/record/authors.tt2" %]
+</div>
 
-                </div>
-            </td>
+[%- IF openurl.enabled == 'true';
+    sfx = []
+    FOR issn IN args.issns;
+        sfx = sfx.import(ResolverResolver.resolve_issn(issn, openurl.baseurl));
+    END;
+    IF sfx.size && sfx.0 != '';
+%]
+    <div id='rdetail_openurl'>
+        <strong class='rdetail_openurl_title'>[% l("Electronic resources") %]</strong>
+        <table><tbody>
+[%-
+        FOR res IN sfx;
+%]
+        <tr>
+            <td class='rdetail_openurl_entry'><a href="[% res.target_url %]">[% res.public_name %]</a></td>
+            <td>[% res.target_coverage %]</td>
         </tr>
-    </tbody>
-</table>
-<br />
-
-[% FOR uri IN args.uris %]
+    [%- END %]
+    </tbody></table>
+[%- END %]
+[%- IF sfx.size && sfx.0 != '' %]
+    </div>    
+[%- END %]
+[%- FOR uri IN args.uris; %]
 <div class="rdetail_uri">
     <a href="[% uri.href %]">[% uri.link %]</a>[% ' - ' _ uri.note IF uri.note %]
 </div>
+[%- END %]
+[%- # hold/copy summary %]
+[%- IF ctx.copy_summary.0.count %]
+<div class="rdetail_copy_counts">
+    <span>
+        [%- l("[quant,_1,current hold,current holds] with [quant,_2,total copy,total copies].", 
+            ctx.record_hold_count, ctx.copy_summary.0.count) %]
+    </span>
+    <span>[% l('[quant,_1,copy,copies] currently available.', ctx.copy_summary.0.available) %]</span>
+</div>
 [% END %]
 
-<br />
-
+[%- IF ctx.copy_summary.0.count %]
+<div id='rdetail_copies'>
+<h2>[% l('Copies') %]</h2>
 <table cellpadding="0" cellspacing="0" border="0" width="100%" id="rdetails_status">
     <thead>
         <tr>
-            <td>[% l("Location") %]</td>
-            <td>[% l("Call Number") %]</td>
-            <td>[% l("Barcode") %]</td>
-            <td>[% l("Shelving Location") %]</td>
-            [% IF ctx.is_staff %]
-            <td>[% l("Age Hold Protection") %]</td>
-            <td>[% l("Create Date") %]</td>
-            <td>[% l("Holdable?") %]</td>
-            [% END %]
-            <td>[% l("Status") %]</td>
-            <td>[% l("Due Date") %]</td>
+            <th id='copy_header_library'>[% l("Location") %]</th>
+            <th id='copy_header_callnmber'>[% l("Call Number") %]</th>
+            <th id='copy_header_barcode'>[% l("Barcode") %]</th>
+            <th id='copy_header_shelfloc'>[% l("Shelving Location") %]</th>
+            [%- IF ctx.is_staff %]
+            <th id='copy_header_age_hold'>[% l("Age Hold Protection") %]</th>
+            <th id='copy_header_create_date'>[% l("Create Date") %]</th>
+            <th id='copy_header_holdable'>[% l("Holdable?") %]</th>
+            [%- END %]
+            <th id='copy_header_status'>[% l("Status") %]</th>
+            <th id='copy_header_due_date'>[% l("Due Date") %]</th>
         </tr>
     </thead>
     <tbody class="copy_details_table">
-        [% last_cn = 0;
+        [%- last_cn = 0;
         FOR copy_info IN ctx.copies;
             NEXT IF copy_info.call_number_label == '##URI##' %]
         <tr>
-            <td>
+            <td header='copy_header_library'>
             [%-
                 org_name = ctx.get_aou(copy_info.circ_lib).name;
                 org_name | html
             -%]
             </td>
-            <td>[% copy_info.call_number_label | html %]</td>
-            <td>[% copy_info.barcode | html %]</td>
-            <td>[% copy_info.copy_location | html %]</td>
-            [% IF ctx.is_staff %]
-            <td>
+            <td header='copy_header_callnumber'>[% copy_info.call_number_label | html %]</td>
+            <td header='copy_header_barcode'>[% copy_info.barcode | html %]</td>
+            <td header='copy_header_shelfloc'>[% copy_info.copy_location | html %]</td>
+            [%- IF ctx.is_staff %]
+            <td header='copy_header_age_hold'>
                 [% copy_info.age_protect ?
                     ctx.get_crahp(copy_info.age_protect).name : l('None') | html %]
             </td>
-            <td>[% date.format(
+            <td header='copy_header_date_format'>[% date.format(
                 ctx.parse_datetime(copy_info.create_date),
                 DATE_FORMAT
             ) %]</td>
-            <td>[%  # Show copy/volume hold links to staff (without
+            <td header='copy_header_holdable'>[%  # Show copy/volume hold links to staff (without
                     # checking whether they have permissions to do those).
                     overall_holdable = (copy_info.holdable == 't' AND
                         copy_info.location_holdable == 't' AND
                     IF overall_holdable;
                         l("Place on"); %]
                 <a href="[% mkurl(ctx.opac_root _ '/place_hold', {hold_target => copy_info.id, hold_type => 'C'}) %]">[% l("copy") %]</a>
-                [%      IF copy_info.call_number != last_cn;
+                [%-      IF copy_info.call_number != last_cn;
                             last_cn = copy_info.call_number;
                             l(" / "); %]
                 <a href="[% mkurl(ctx.opac_root _ '/place_hold', {hold_target => copy_info.call_number, hold_type => 'V'}) %]">[% l("volume") %]</a>
-                [%      END;
+                [%-      END;
                     ELSE;
                         l("No");
                     END %]</td>
-            [% END %]
-            <td>[% copy_info.copy_status | html %]</td>
-            <td>[%
+            [%- END %]
+            <td header='copy_header_status'>[% copy_info.copy_status | html %]</td>
+            <td header='due_date'>[%
                 IF copy_info.due_date;
                     date.format(
                         ctx.parse_datetime(copy_info.due_date),
                     '-';
                 END %]</td>
         </tr>
-        [% END %]
+        [%- END %]
         <tr>
-        [% IF ctx.copy_offset > 0;
+        [%- IF ctx.copy_offset > 0;
             new_offset = ctx.copy_offset - ctx.copy_limit;
             IF new_offset < 0; new_offset = 0; END %]
             <td>
                 <a href="[% mkurl('', {copy_offset => new_offset, copy_limit => ctx.copy_limit}) %]">&laquo; [%
                     l('Previous [_1]', ctx.copy_offset - new_offset) %]</a>
             </td>
-        [% END %]
-        [% IF ctx.copies.size >= ctx.copy_limit %]
+        [%- END %]
+        [%- IF ctx.copies.size >= ctx.copy_limit %]
             <td>
                 <a href="[% mkurl('', {copy_offset => ctx.copy_offset + ctx.copy_limit, copy_limit => ctx.copy_limit}) %]">[%
                     l('Next [_1]', ctx.copy_limit) %] &raquo;</a>
             </td>
-        [% END %]
+        [%- END %]
         </tr>
         <tr>
             <td>
-                [% more_copies_limit = 50 %] [%# TODO: config %]
-                [% IF  ctx.copy_limit != more_copies_limit AND ctx.copies.size >= ctx.copy_limit %]
+                [%- more_copies_limit = 50 %] [%# TODO: config %]
+                [%- IF  ctx.copy_limit != more_copies_limit AND ctx.copies.size >= ctx.copy_limit %]
                     <div style='margin-top:10px;'>
                         <img src="[% ctx.media_prefix %]/images/plus_sign.png" />
                         <a href="[% mkurl('', {copy_limit => more_copies_limit, copy_offset => 0}) %]">[% l('Show more copies') %]</a>
                     </div>
-                [% ELSIF ctx.copy_limit == more_copies_limit %]
+                [%- ELSIF ctx.copy_limit == more_copies_limit %]
                     <div style='margin-top:10px;'>
                         <img src="[% ctx.media_prefix %]/images/minus_sign.png" />
                         <a href="[% mkurl('', {copy_limit => 0, copy_offset => 0}) %]">[% l('Show fewer copies') %]</a>
                     </div>
-                [% END %]
-            </td>
-        </tr>
-        <tr>
-            <td>
-                [% IF CGI.param('expand') == 'all' %]
-                    <img src="[% ctx.media_prefix %]/images/minus_sign.png" />
-                    <a href="[% mkurl('', {}, ['expand']) %]">[% l('Collapse all tabs') %]</a>
-                [% ELSE %]
-                    <img src="[% ctx.media_prefix %]/images/plus_sign.png" />
-                    <a href="[% mkurl('', {expand => 'all'}) %]">[% l('Expand all tabs') %]</a>
-                [% END %]
+                [%- END %]
             </td>
         </tr>
-
     </tbody>
 </table>
-
-<div id="rdetail_extras_expand" class="hide_me">
-    <a href="#"><img
-        src="[% ctx.media_prefix %]/images/plus_sign.png" /></a>
-    <a style="position:relative;top:-3px;" href="#">[% l('Expand all tabs') %]</a>
 </div>
+[%- END %]
 
-<div id="rdetail_extras_collapse" class="hide_me">
-    <a href="#"><img src="[% ctx.media_prefix %]/images/plus_sign.png" /></a>
-    <a style="position:relative;top:-3px;" href="#">[% l('Collapse all tabs') %]</a>
-</div>
+<h2 id='rdetail_record_details'>[% l("Record details") %]</h2>
+<ul>
+    [%- IF attrs.isbns.0; FOR isbn IN attrs.isbns %]
+    <li class='rdetail_isbns'>
+        <strong class='rdetail_label'>[% l('ISBN:'); %]</strong>
+        <span class='rdetail_value'>[% isbn | html  %]</span>
+    </li>
+        [%- END %]
+    [%- END %]
+    [%- IF attrs.issns.0; FOR issn IN attrs.issns %]
+    <li class='rdetail_issns'>
+        <strong class='rdetail_label'>[% l('ISSN:'); %]</strong>
+        <span class='rdetail_value'>[% issn | html  %]</span>
+    </li>
+        [%- END %]
+    [%- END %]
+    [%- IF attrs.phys_desc %]
+    <li id='rdetail_phys_desc'>
+        <strong class='rdetail_label'>[% l("Physical Description:") %]</strong>
+        <span class='rdetail_value'>[% attrs.phys_desc | html %]</span>
+    </li>
+    [%- END %]
+    [%- IF attrs.edition %]
+    <li id='rdetail_edition'>
+        <strong class='rdetail_label'>[% l("Edition:") %]</strong>
+        <span class='rdetail_value'>[% attrs.edition | html %]</span>
+    </li>
+    [%- END %]
+    [%- IF attrs.publisher %]
+    <li id='rdetail_publisher'>
+        <strong class='rdetail_label'>[% l("Publisher:") %]</strong>
+        <span class='rdetail_value'>[% attrs.publisher | html %] [% IF attrs.pubdate; attrs.pubdate | html; END %]</span>
+    </li>
+    [%- END %]
+</ul>
+
+[%- INCLUDE "opac/parts/record/subjects.tt2" %]
+[%- INCLUDE "opac/parts/record/series.tt2" %]
+[%- INCLUDE "opac/parts/record/extras.tt2" %]
diff --git a/Open-ILS/src/templates/opac/parts/result/facets.tt2 b/Open-ILS/src/templates/opac/parts/result/facets.tt2
new file mode 100644 (file)
index 0000000..76c335f
--- /dev/null
@@ -0,0 +1,99 @@
+<div class="facet_box_wrapper">
+[% 
+
+close_facets = CGI.param('close_facet') || [];
+selected_facets = CGI.param('facet') || [];
+
+# collect facet type labels for easier sorting
+labels = []; 
+FOR facet IN ctx.search_facets.values;
+    labels.push(facet.cmf.label);
+END;
+
+FOR facet_label IN labels.sort;
+    FOR facet IN ctx.search_facets.values;
+        IF facet.cmf.label == facet_label;
+            fclass = facet.cmf.field_class;
+            fname = facet.cmf.name;
+            close_key = fclass _ fname %]
+
+        <div class="facet_box_temp">
+            <div class="header">
+                <div class="title">[% facet.cmf.label %]</div>
+                <div class="button">
+
+                    [% IF close_facets.grep(close_key).0;
+                        new_close = [];
+                        FOR fct IN close_facets;
+                            IF fct != close_key;
+                                new_close.push(fct);
+                            END;
+                        END;
+                        expand_url = mkurl('', {close_facet => new_close});
+                        IF new_close.size == 0;
+                            expand_url  = mkurl('', {}, ['close_facet']);
+                        END;
+                    %]
+                        <a href="[% expand_url %]"><img 
+                            src="[% ctx.media_prefix %]/images/adv_search_plus_btn.png" alt="[% l('Expand') %]" /></a>
+                    [% ELSE %]
+                        <a href="[% mkurl('', {close_facet => close_facets.merge([close_key])}) %]"><img 
+                            src="[% ctx.media_prefix %]/images/adv_search_minus_btn.png" alt="[% l('Collapse') %]" /></a>
+                    [% END %]
+                </div>
+                <div class="clear">&nbsp;</div>
+            </div>
+            [% IF !close_facets.grep(close_key).0 %]
+            <div class="box_wrapper">
+                <div class="box">
+                [% FOR facet_data IN facet.data;
+                    display_value = facet_data.value | html;
+                    param_string = fclass _ '|' _ fname _ '[' _ facet_data.value _ ']';
+                    new_facets = [];
+                    this_selected = 0;
+                    FOR selected IN selected_facets;
+                        IF selected == param_string; 
+                            this_selected = 1; 
+                        ELSE;
+                            new_facets.push(selected);
+                        END;
+                    END;
+                    IF this_selected;
+                        # This facet is already selected by the user. 
+                        # Link removes the facet from the set of selected facets.
+                    %] 
+                        <div class="facet_template facet_template_selected">
+                            <div class="facet">
+                                [% IF new_facets.size == 0 %]
+                                <a href="[% mkurl('', {}, ['facet']) %]">[% display_value %]</a>
+                                [% ELSE %]
+                                <a href="[% mkurl('', {facet => new_facets}) %]">[% display_value %]</a>
+                                [% END %]
+                            </div>
+                            <div class="count">([% facet_data.count %])</div>
+                            <div class="clear">&nbsp;</div>
+                        </div>
+                    [% 
+                        ELSE;
+                        # This facet is not currently selected.  If selected, 
+                        # append this facet to the list of currently active facets.
+                    %]
+                        <div class="facet_template">
+                            <div class="facet">
+                                <a href='[% mkurl('', {facet => selected_facets.merge([param_string])}, ['page']) %]'>[% display_value %]</a>
+                            </div>
+                            <div class="count">([% facet_data.count %])</div>
+                            <div class="clear">&nbsp;</div>
+                        </div>
+                    [% END %]
+                [% END %]
+                </div>
+                <div class="clear">&nbsp;</div>
+            </div> <!-- box_wrapper -->
+            [% END %]
+        </div> <!-- facet_box_temp -->
+        [% END %]
+    [% END %]
+[% END %]
+</div> <!-- facet_box_wrapper -->
+
index 0706ef6..b60e647 100644 (file)
@@ -19,8 +19,8 @@
     <table cellpadding="0" cellspacing="0" border="0" width="100%">
         <tr>
             <td valign="top" width="1" style="padding-right:20px;">
-                <div style="width:174px;" class="hide_me">
-                    SIDEBAR TODO
+                <div style="width:174px;">
+                    [% INCLUDE 'opac/parts/result/facets.tt2' %]
                 </div>
             </td>
             <td class='opac-auto-015' width="1"></td>
index 1f1f0a8..907a0e7 100644 (file)
@@ -3,60 +3,40 @@
     [% UNLESS took_care_of_form -%]
     <form action="[% ctx.opac_root %]/results" method="GET">
     [%- END %]
-    <table cellpadding="0" cellspacing="10" border="0">
-        <tr>
-            <td colspan="3">
-                <span class="search_catalog_lbl">[% l('Search the Catalog') %]</span>
-                <a href="[% ctx.opac_root %]/advanced"
-                    id="home_adv_search_link"><span
-                    class="adv_search_font">[% l('Advanced Search') %]</span></a>
-            </td>
-        </tr>
-        <tr>
-            [% IF is_advanced || is_special %]
-            <td colspan="2">
-                <input type="hidden" name="_adv" value="1" />
-            [% ELSE %]
-            <td>
-            [% INCLUDE "opac/parts/qtype_selector.tt2" %]
-            </td>
-            [% END %]
-            [% IF ctx.processed_search_query OR (NOT is_advanced AND NOT is_special) %]
-            <td>
-                <div id="search_box_wrapper">
-                    <!-- Note: when common browsers support HTML5 placeholder text, we can remove the JS -->
-                    <input type="text" id="search_box" name="query" value="[% is_advanced ? ctx.processed_search_query : CGI.param('query') || l("Search Keyword") | html %]"
-                        [% IF is_advanced %]style="width: 450px"[% END %]
-                        onfocus="if (this.value=='[% l("Search Keyword") %]'){this.value='';this.style.color='#000';}"
-                        onblur="if (this.value==''){this.value='[% l("Search Keyword") %]';this.style.color='#999';}"
-                        x-webkit-speech />
-                </div>
-                <input name='page' type='hidden' value="0" />
-            </td>
-            <td valign="top">
-                <div class="pos-abs">
-                    <div class="opac-auto-143">
-                        <input id='search-submit-go' type="submit" value="[% l('Search') %]" alt="[% l('Search') %]" class="opac-button"
-                            onclick='setTimeout(function(){$("search-submit-spinner").className=""; $("search-submit-go").className="hidden"}, 2000)'/>
-                        <img id='search-submit-spinner' src='/opac/images/progressbar_green.gif' style='height:16px;width:16px;' class='hidden' alt=''/>
-                    </div>
-                </div>
-            </td>
-            [% END %]
-        </tr>
-        [% UNLESS is_advanced OR is_special %]
-        <tr>
-            <td>
-                [% INCLUDE "opac/parts/coded_value_selector.tt2" attr=["mattype", "item_type"] none_ok=1 none_label=l('All Formats') %]
-            </td>
-            <td>
-                <span>
-                    [% PROCESS build_org_selector name='loc' value=CGI.param('loc') %]
-                </span>
-            </td>
-        </tr>
+    <div>
+        <span class="search_catalog_lbl">[% l('Search the Catalog') %]</span>
+        <a href="[% mkurl(ctx.opac_root _ '/advanced') %]"
+            id="home_adv_search_link"><span
+            class="adv_search_font">[% l('Advanced Search') %]</span></a>
+    </div>
+    <div style="font-weight: bold">[%- l('Search ');
+        INCLUDE "opac/parts/coded_value_selector.tt2"
+            attr=["mattype", "item_type"] none_ok=1 none_label=l('All Formats');
+            l(' for ');
+        %]
+        <span class='search_box_wrapper'>
+            <input type="text" id="search_box" name="query" value="[% is_advanced ? ctx.processed_search_query : CGI.param('query') || l("Search Keyword") | html %]"
+                onfocus="if (this.value=='[% l("Search Keyword") %]'){this.value='';this.style.color='#000';}"
+                onblur="if (this.value==''){this.value='[% l("Search Keyword") %]';this.style.color='#999';}"
+                x-webkit-speech />
+        </span>
+        [%- INCLUDE "opac/parts/qtype_selector.tt2";
+            l(' in '); PROCESS build_org_selector name='loc' value=CGI.param('loc');
+    %]
+    <span>
+        <input id='search-submit-go' type="submit" value="[% l('Search') %]" alt="[% l('Search') %]" class="opac-button"
+            onclick='setTimeout(function(){$("search-submit-spinner").className=""; $("search-submit-go").className="hidden"}, 2000)'/>
+        <img id='search-submit-spinner' src='/opac/images/progressbar_green.gif' style='height:16px;width:16px;' class='hidden' alt=''/>
+    </span>
+    </div>
+    [% IF is_advanced || is_special %]
+    <div>
+        <input type="hidden" name="_adv" value="1" />
+        [% IF ctx.processed_search_query OR (NOT is_advanced AND NOT is_special) %]
+        <input name='page' type='hidden' value="0" />
         [% END %]
-    </table>
+    </div>
+    [%- END %]
     [% UNLESS took_care_of_form %]</form>[% END %]
     [% IF (is_advanced AND NOT is_special) AND CGI.param('qtype') %]
     <div class="opac-auto-102">
index 92ee814..db1f2a4 100644 (file)
@@ -1,4 +1,5 @@
 [% IF !ctx.is_staff %]
+<div id="header-wrap">
 <div id="header">
     <div class="float-left">
         [% INCLUDE "opac/parts/topnav_logo.tt2" %]
         </div>
         [% ELSE %]
         <div id="dash_wrapper">
-            <div class="float-right">
-                <table cellpadding="0" cellspacing="0" border="0">
-                    <tr>
-                        <td>
-                            <img src="[% ctx.media_prefix %]/images/dash-corner-left1.png" />
-                        </td>
-                        <td id="dash_corner_mid1a">
-                            <span id="dash_user">
-                                [%  l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %]
-                            </span>
-                        </td>
-                        <td id="dash_corner_mid1b">
-                            <img src="[% ctx.media_prefix %]/images/dash-divider.jpg" />
-                        </td>
-                        <td id="dash_corner_mid1c">
+            <div id="dash_identity">
+                <span id="dash_user">
+                    [%  l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %]
+                </span>
+                <span class="dash_divider">|</span>
+                <a href="[% ctx.opac_root %]/myopac/main" 
+                    class="opac-button">[% l('My Account') %]</a>
 
-                            <a href="[% ctx.opac_root %]/myopac/main" 
-                                class="opac-button">[% l('My Account') %]</a>
-
-                            <a href="[% ctx.opac_root %]/logout" class="opac-button"
-                                id="logout_link">[% l('Logout') %]</a>
-                        </td>
-                        <td>
-                            <img src="[% ctx.media_prefix %]/images/dash-corner-right1.png" />
-                        </td>
-                    </tr>
-                </table>
+                <a href="[% ctx.opac_root %]/logout" class="opac-button"
+                    id="logout_link">[% l('Logout') %]</a>
             </div>
             <div id="dashboard">
-                <div class="pos-abs">
-                    <div class="pos-rel-top4">
-                        <table cellpadding="0" cellspacing="0" border="0">
-                            <tr>
-                                <td>
-                                    <img src="[% ctx.media_prefix %]/images/dash-corner-left2.png" />
-                                </td>
-                                <td id="dash_corner_mid2a">
-                                    <div id="dash_number_row">
-                                        <div class="pos-abs">
-                                            <div class="dash-pos-out">
-                                                <div class="dash-align-out">
-                                                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/circs"><span id="dash_checked">[% ctx.user_stats.checkouts.total_out %]</span> [% l("Checked Out") %]</a>
-                                                </div>
-                                            </div>
-                                        </div>
-                                        <div class="pos-abs">
-                                            <div class="dash-pos-holds">
-                                                <div class="dash-align-holds">
-                                                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/holds"><span id="dash_holds">[% ctx.user_stats.holds.total %]</span> [% l("On Hold") %]</a>
-                                                </div>
-                                            </div>
-                                        </div>
-                                        <div class="pos-abs">
-                                            <div class="dash-pos-pickup">
-                                                <div class="dash-align-pickup">
-                                                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/holds?available=1"><span id="dash_pickup">[% ctx.user_stats.holds.ready %]</span> [% l("Ready for Pickup") %]</a>
-                                                </div>
-                                            </div>
-                                        </div>
-                                        <div class="pos-abs">
-                                            <div class="dash-pos-fines">
-                                                <div class="dash-align-fines">
-                                                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/main"><span id="dash_fines">[% money(ctx.user_stats.fines.balance_owed) %]</span> [% l("Fines") %]</a>
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </td>
-                                <td>
-                                    <img src="[% ctx.media_prefix %]/images/dash-corner-right2.png" />
-                                </td>
-                            </tr>
-                        </table>
-                    </div>
-                </div>
+                <span class="dash-align">
+                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/circs"><span id="dash_checked">[% ctx.user_stats.checkouts.total_out %]</span> [% l("Checked Out") %]</a>
+                </span>
+                <span class="dash_divider">|</span>
+                <span class="dash-align">
+                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/holds"><span id="dash_holds">[% ctx.user_stats.holds.total %]</span> [% l("On Hold") %]</a>
+                </span>
+                <span class="dash_divider">|</span>
+                <span class="dash-align">
+                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/holds?available=1"><span id="dash_pickup">[% ctx.user_stats.holds.ready %]</span> [% l("Ready for Pickup") %]</a>
+                </span>
+                <span class="dash_divider">|</span>
+                <span class="dash-align">
+                    <a class="dash-link" href="[% ctx.opac_root %]/myopac/main"><span id="dash_fines">[% money(ctx.user_stats.fines.balance_owed) %]</span> [% l("Fines") %]</a>
+                </span>
             </div>
         </div>
         [% END %]
     </div>
     <div class="common-no-pad"></div>
 </div>
+</div>
 [% END %]
 [% INCLUDE "opac/parts/topnav_links.tt2" %]
index 7e479f9..cd067d6 100644 (file)
@@ -1,2 +1,2 @@
         <a href="http://evergreen-ils.org"><img alt="[% l('Evergreen Logo') %]" 
-            src="[% ctx.media_prefix %]/opac/images/small_logo.jpg" /></a>
+            src="[% ctx.media_prefix %]/opac/images/small_logo.png" /></a>
index bbe6951..3c8d89e 100644 (file)
@@ -3,7 +3,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Place Hold") %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <div id="content-wrapper">
index 5c1e4d8..59b7640 100644 (file)
@@ -4,7 +4,6 @@
     INCLUDE "opac/parts/topnav.tt2";
     ctx.page_title = l("Record Detail") %]
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" %]
     </div>
     <br class="clear-both" />
@@ -18,7 +17,7 @@
         [% END %]
         <div id="[% ctx.staff_saved_search_size ? 'main-content-after-bar' : 'main-content' %]">
             [% INCLUDE "opac/parts/record/body.tt2" %]
-            <div class="common-full-pad"></div>        
+            <div class="common-full-pad"></div>
         </div>
         <br class="clear-both" />
     </div>
index 8876a50..b9ec15f 100644 (file)
@@ -18,7 +18,6 @@
 %]
     <form action="[% ctx.opac_root %]/results" method="GET">
     <div id="search-wrapper">
-        [% INCLUDE "opac/parts/printnav.tt2" %]
         [% INCLUDE "opac/parts/searchbar.tt2" took_care_of_form=1 %]
     </div>
     <div class="almost-content-wrapper">
index cc8dcf4..449a4b2 100644 (file)
@@ -2,7 +2,7 @@ body {
        margin:0;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 12px;
-       background:#333;
+       background: #252525;
 }
 
 img {
@@ -11,11 +11,6 @@ img {
 
 a {
        color: #003399;
-       text-decoration: none;
-}
-
-a:hover {
-       text-decoration: underline;
 }
 
 #search-wrapper input[type=text] {
@@ -54,7 +49,7 @@ h1 {
 h2 {
        margin:0;
        margin-bottom: 5px;
-       font-size: 14px;
+       font-size: 16px;
        font-weight:bold;
 }
 
@@ -84,21 +79,39 @@ div.select-wrapper:hover {
 }
 
 #dash_wrapper {
-       width:500px;
-       position:relative;
-       top:-26px;
+       position: relative;
+    top: -2em;
+}
+
+#dash_wrapper div {
+       position: relative;
+    vertical-align: middle;
+    background: #252525;
+    border-radius: 5px;
+    height: 3em;
+    padding: 0em 1em 0em 1em;
+}
+
+span.dash_divider {
+    margin: 0em 1em 0em 1em;
+       position: relative;
+    top: 10px;
+    color: black;
 }
 
 #dashboard {
        clear:both;
        float:right;
-       width:384px;
+    margin-top: 1em;
+    background: #252525;
+    border-radius: 5px;
+    height: 3em;
 }
 
-#dashboard span {
-       font-weight:bold;
-       position:relative;
-       left:-1px;
+#dashboard span.dash-align a {
+       font-weight: bold;
+       position: relative;
+    top: 10px;
 }
 
 #dash_user {
@@ -108,44 +121,22 @@ div.select-wrapper:hover {
     top: 10px;
 }
 
-#dash_corner_mid1a {
-    vertical-align: top;
-    background: url('/images/dash-corner-mid1.png') repeat-x;
-    padding-left: 8px;
-}
-#dash_corner_mid1b {
-    background: url('/images/dash-corner-mid1.png') repeat-x;
-    padding: 0px 8px 0px 10px;
-}
-#dash_corner_mid1b img { position: relative; top: -1px; }
-#dash_corner_mid1c {
-    background: url('/images/dash-corner-mid1.png') repeat-x;
-    vertical-align: top;
-}
-#dash_corner_mid2a {
-    vertical-align: top;
-    width: 372px;
-    background: url('/images/dash-corner-mid2.png') repeat-x;
-}
-.dash-pos-out { position: relative; left: 3px; }
-.dash-pos-holds { position: relative; left: 100px; }
-.dash-align-out { text-align: right; width: 86px; }
-.dash-align-holds { text-align: right; width: 62px; }
-.dash-pos-pickup { position: relative; left: 170px; }
-.dash-align-pickup { text-align: right; width: 111px; }
-.dash-pos-fines { position: relative; left: 284px; }
-.dash-align-fines { text-align: right; width: 76px; }
-.pos-rel-top4 { position: relative; top: 4px; }
-#dash_number_row { position: relative; top: 6px; }
 #logout_link { left: 1px; }
 
 #dash_checked { color: #ffcc33; }
 #dash_holds { color: #ffcc33; }
 #dash_pickup { color: #1dd93c; }
 #dash_fines { color: #f41d36; }
+#header-wrap {
+    background: linear-gradient(lightGreen, #252525);
+    background: -moz-linear-gradient(lightGreen, #252525);
+    background: -o-linear-gradient(lightGreen, #252525);
+    background: -webkit-linear-gradient(lightGreen, #252525);
+    background-color: lightGreen;
+}
 #header {
        color: #fff;
-       padding: 26px 0px 26px 0px;
+       padding-top: 26px;
        width: 974px;
        margin: auto;
        font-size:11px;
@@ -225,7 +216,7 @@ div.select-wrapper:hover {
        background: white;
 }
 
-#search_box_wrapper {
+.search_box_wrapper {
        border:1px solid #e9ebf3;
        padding: 1px;
     padding-left: 3px;
@@ -287,8 +278,8 @@ div.select-wrapper:hover {
     display: block;
     margin: 10px 7px 10px 0px;
     padding: 10px 0px 10px 0px;
-    -moz-border-radius: 5%
-    border-radius: 5%;
+    -moz-border-radius: 7px 7px 0px 0px
+    border-radius: 7px 7px 0px 0px;
     font-weight: bold;
     color: #45709b;
     background: #9ad0f1;
@@ -355,10 +346,6 @@ div.select-wrapper:hover {
        margin-top: 15px;
 }
 
-#rdetail_title {
-       font-size: 18px;
-}
-
 #rdetail_image { border: none; }
 #rdetail_image_cell {
        padding-top: 3px;
@@ -384,21 +371,28 @@ div.select-wrapper:hover {
        padding-right: 50px;
 }
 
+#rdetail_copies {
+    clear: both;
+    padding-top: 1.5em;
+}
+
 #rdetails_status td, #rdetails_status2 td {
        white-space:nowrap !important;
        padding: 7px 0px 3px 13px;
 }
 
-#rdetails_status thead td {
+#rdetails_status thead th {
        background-color: #d8d8d8;
        padding: 13px 0px 13px 13px;
        font-size: 10px;
        text-transform: uppercase;
        font-weight: bold;
+    text-align: left;
 }
 
 #rdetails_status tbody td {
-       padding-left: 13px;;
+       padding-left: 13px;
+    text-align: left;
 }
 
 .rdetail_extras {
@@ -881,6 +875,7 @@ div.select-wrapper:hover {
 }
 
 /* some facet styling */
+/*
 .facetClassContainer { margin: 2px; border: 1px solid #CCC; }
 .facetClassLabelContainer { border: 1px solid #CCC; }
 .facetClassLabel { font-weight: bold; text-align: center; }
@@ -892,20 +887,95 @@ div.select-wrapper:hover {
 .facetField { border-top: 1px solid #CCC; }
 .facetFields { padding-left: 5px; }
 .facetFieldLineValue { overflow: hidden; text-overflow: ellipsis; }
+*/
+
+.facet_box_temp {
+       padding-bottom:3px;
+       width:180px;
+       overflow:hidden;
+}
+
+.facet_box_temp .header {
+       width:180px;
+       height:31px;
+       overflow:hidden;
+       background:url('/images/facet_box_bg.png') no-repeat;
+       font-weight:bold;
+       color:#074079;
+       padding-top:4px;
+}
+
+.facet_box_temp .header .title {
+       float:left;
+       padding-top:6px;
+       padding-left:12px;
+       width:134px;
+       overflow:hidden;
+}
+
+.facet_box_temp .header .button {
+       float:right;
+       padding-right:6px;
+}
+
+.facet_box_wrapper .box_wrapper {
+       position:relative;
+       top:-4px;
+       margin-bottom:-5px;
+       *margin-bottom:-6px;
+}
+
+.facet_box_wrapper .box_wrapper .box {
+       width:166px;
+       border-top:1px solid #7ebee5;
+       border-left:1px solid #f3f3f3;
+       border-right:1px solid #f3f3f3;
+       background:white;
+       padding-left:12px;
+       padding-top:6px;
+}
+
+.facet_box_wrapper .box_wrapper .bottom {
+       background:url('/images/facet_box_bg_bottom.png') no-repeat;
+}
+
+.facet_template {
+       padding-bottom:5px;
+}
+
+.facet_template .facet {
+       float:left;
+       width:124px;
+}
+
+.facet_template .count {
+       float:right;
+       color:#818080;
+       padding-right:11px;
+}
+
+.facet_template_selected {
+    background-color: #d7d7d7;
+}
+
+#footer-wrap {
+    background: linear-gradient(lightGreen, #252525);
+    background: -moz-linear-gradient(lightGreen, #252525);
+    background: -o-linear-gradient(lightGreen, #252525);
+    background: -webkit-linear-gradient(lightGreen, #252525);
+    background-color: lightGreen;
+}
 
 #footer {
        padding-top:5px;
        padding-bottom: 10px;
-       color: white;
        margin: auto;
        width: 974px;
-       color: #afafaf;
        font-size: 11px;
 }
 
 #footer a {
-       color: white;
-       color: #afafaf;
+       color: black;
 }
 
 .color_4 {
@@ -1044,7 +1114,38 @@ a.opac-button {
     width: 100%;
 }
 
+.rdetail_copy_counts {
+    margin-top: 1em;
+}
+
+#rdetail_record_details {
+    clear: both;
+    margin-top: 1em;
+}
+
+.rdetail_subject_type {
+    vertical-align: top;
+    font-weight: bold;
+}
+
 .bookbag-item-row td { vertical-align: top; }
 
 .error { color: red; font-weight: bold; }
 .success { color: green; font-weight: bold; }
+
+.rdetail_related_subjects {
+    margin-top: 1.5em;
+}
+
+.rdetail_related_series {
+    margin-top: 1.5em;
+}
+
+#rdetail_openurl {
+    margin-top: 1em;
+}
+
+.rdetail_openurl_entry {
+    margin-left: 1em;
+    padding-left: 1em;
+}
diff --git a/Open-ILS/web/images/adv_search_minus_btn.png b/Open-ILS/web/images/adv_search_minus_btn.png
new file mode 100644 (file)
index 0000000..4050595
Binary files /dev/null and b/Open-ILS/web/images/adv_search_minus_btn.png differ
diff --git a/Open-ILS/web/images/adv_search_plus_btn.png b/Open-ILS/web/images/adv_search_plus_btn.png
new file mode 100644 (file)
index 0000000..7f1ae93
Binary files /dev/null and b/Open-ILS/web/images/adv_search_plus_btn.png differ
diff --git a/Open-ILS/web/images/dash-corner-left1.png b/Open-ILS/web/images/dash-corner-left1.png
deleted file mode 100644 (file)
index 9559e8b..0000000
Binary files a/Open-ILS/web/images/dash-corner-left1.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-corner-left2.png b/Open-ILS/web/images/dash-corner-left2.png
deleted file mode 100644 (file)
index 5bc8112..0000000
Binary files a/Open-ILS/web/images/dash-corner-left2.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-corner-mid1.png b/Open-ILS/web/images/dash-corner-mid1.png
deleted file mode 100644 (file)
index 546b8a8..0000000
Binary files a/Open-ILS/web/images/dash-corner-mid1.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-corner-mid2.png b/Open-ILS/web/images/dash-corner-mid2.png
deleted file mode 100644 (file)
index 15af112..0000000
Binary files a/Open-ILS/web/images/dash-corner-mid2.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-corner-right1.png b/Open-ILS/web/images/dash-corner-right1.png
deleted file mode 100644 (file)
index 061afc9..0000000
Binary files a/Open-ILS/web/images/dash-corner-right1.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-corner-right2.png b/Open-ILS/web/images/dash-corner-right2.png
deleted file mode 100644 (file)
index 8663e0c..0000000
Binary files a/Open-ILS/web/images/dash-corner-right2.png and /dev/null differ
diff --git a/Open-ILS/web/images/dash-divider.jpg b/Open-ILS/web/images/dash-divider.jpg
deleted file mode 100644 (file)
index 19dda7d..0000000
Binary files a/Open-ILS/web/images/dash-divider.jpg and /dev/null differ
diff --git a/Open-ILS/web/images/facet_box_bg.png b/Open-ILS/web/images/facet_box_bg.png
new file mode 100644 (file)
index 0000000..996f0a8
Binary files /dev/null and b/Open-ILS/web/images/facet_box_bg.png differ
diff --git a/Open-ILS/web/images/facet_box_bg_bottom.png b/Open-ILS/web/images/facet_box_bg_bottom.png
new file mode 100644 (file)
index 0000000..fc776c3
Binary files /dev/null and b/Open-ILS/web/images/facet_box_bg_bottom.png differ
index 23ed428..1178515 100644 (file)
@@ -183,19 +183,80 @@ function AcqLiTable() {
     };
 
 
+    this.getAll = function(callback, id_only) {
+        /* For some uses of the li table, we may not really know about "all"
+         * the lineitems that the user thinks we know about. If we're a paged
+         * picklist, for example, we only know about the lineitems we've
+         * displayed, but not necessarily all the lineitems on the picklist.
+         * So we reach out to pcrud to inform us.
+         */
+
+        var oncomplete = function(r) {
+            var id_list = openils.Util.readResponse(r);
+            if (id_only)
+                callback(id_list);
+            else
+                self.fetchLineitemsById(id_list, callback);
+        };
+
+        if (this.isPL) {
+            this.pcrud.search(
+                "jub", {"picklist": this.isPL}, {
+                    "id_list": true,    /* sic, even if id_only */
+                    "async": true,
+                    "oncomplete": oncomplete
+                }
+            );
+            return;
+        } else if (this.isPO) {
+            this.pcrud.search(
+                "jub", {"purchase_order": this.isPO}, {
+                    "id_list": true,
+                    "async": true,
+                    "oncomplete": oncomplete
+                }
+            );
+            return;
+        } else if (this.isUni && this.pager) {
+            this.pager.getAllLineitemIDs(oncomplete);
+            return;
+        }
+
+        /* If execution reaches this point, we don't need or can't perform
+         * any special tricks to find out the "real" list of "all" lineitems
+         * in this context, so we fall back to the old method.
+         */
+        callback(this.getSelected(true, null, id_only));
+    };
+
     /** @param all If true, assume all are selected */
-    this.getSelected = function(all) {
-        var selected = [];
+    this.getSelected = function(
+        all,
+        callback /* If you want a "good" idea of "all" lineitems, you must
+        provide a callback that accepts an array parameter, rather than
+        relying on the return value of this method itself. */,
+        id_only
+    ) {
+        if (all && callback)
+            return this.getAll(callback, id_only);
+
         var indices = {};   /* use to uniqify. needed in paging situations. */
-        dojo.forEach(self.selectors, 
+        dojo.forEach(this.selectors,
             function(i) { 
                 if(i.checked || all)
                     indices[i.parentNode.parentNode.getAttribute('li')] = true;
             }
         );
-        return openils.Util.objectProperties(indices).map(
-            function(liId) { return self.liCache[liId]; }
-        );
+
+        var result = openils.Util.objectProperties(indices);
+
+        if (!id_only)
+            result = result.map(function(liId) { return self.liCache[liId]; });
+
+        if (callback)
+            callback(result);
+        else
+            return result;
     };
 
     this.setRowAttr = function(td, liWrapper, field, type) {
@@ -261,8 +322,8 @@ function AcqLiTable() {
         var row = self.rowTemplate.cloneNode(true);
         if (!skip_final_placement) {
             self.tbody.appendChild(row);
-            self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
         }
+        self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
 
         // sort the lineitem notes on edit_time
         if(!li.lineitem_notes()) li.lineitem_notes([]);
@@ -816,6 +877,27 @@ function AcqLiTable() {
         );
     };
 
+    /* For a given list of lineitem ids, build a list of full lineitems
+     * re-using the fetching logic that is otherwise typical to use in this
+     * module.
+     *
+     * If we've already got a lineitem in the cache, just use that.
+     *
+     * Once we've built a list of lineitems, call callback(thatlist).
+     */
+    this.fetchLineitemsById = function(id_list, callback) {
+        var total = id_list.length;
+        var result_list = [];
+
+        var inner = function(li) {
+            result_list.push(li)
+            if (--total <= 0)
+                callback(result_list);
+        };
+
+        id_list.forEach(function(id) { self._fetchLineitem(id, inner); });
+    };
+
     this._fetchLineitem = function(liId, handler, force) {
 
         var li = this.liCache[liId];
@@ -2253,26 +2335,42 @@ function AcqLiTable() {
 
 
     this._createPO = function(fields) {
-        this.show('acq-lit-progress-numbers');
-        var po = new fieldmapper.acqpo();
-        po.provider(this.createPoProviderSelector.attr('value'));
-        po.ordering_agency(this.createPoAgencySelector.attr('value'));
-        po.prepayment_required(fields.prepayment_required[0] ? true : false);
+        var wantall = (fields.create_from == "all");
+
+        /* If we're a picklist or purchase order already and the user wants
+         * all lineitems, we might have pages' worth of lineitems haven't all
+         * been loaded yet, so getSelected() won't find them.  The server,
+         * however, should know about all our lineitems, so let's ask the
+         * server for a complete list.
+         */
+
+        if (wantall) {
+            this.getSelected(
+                true, function(list) {
+                    self._createPOFromLineitems(fields, list);
+                }, /* id_list */ true
+            );
+        } else {
+            this._createPOFromLineitems(fields, this.getSelected(false, null, true /* id_list */));
+        }
+    };
 
-        var selected = this.getSelected( (fields.create_from == 'all') );
-        if(selected.length == 0) return;
+    this._createPOFromLineitems = function(fields, selected) {
+        if (selected.length == 0) return;
 
-        var max = selected.length * 3;
+        this.show("acq-lit-progress-numbers");
+        var po = new fieldmapper.acqpo();
+        po.provider(this.createPoProviderSelector.attr("value"));
+        po.ordering_agency(this.createPoAgencySelector.attr("value"));
+        po.prepayment_required(fields.prepayment_required[0] ? true : false);
 
-        var self = this;
         fieldmapper.standardRequest(
-            ['open-ils.acq', 'open-ils.acq.purchase_order.create'],
+            ["open-ils.acq", "open-ils.acq.purchase_order.create"],
             {   async: true,
                 params: [
                     openils.User.authtoken, 
-                    po, 
-                    {
-                        lineitems : selected.map(function(li) { return li.id() }),
+                    po, {
+                        lineitems : selected,
                         create_assets : fields.create_assets[0],
                     }
                 ],
@@ -2280,12 +2378,15 @@ function AcqLiTable() {
                 onresponse : function(r) {
                     var resp = openils.Util.readResponse(r);
                     self._updateProgressNumbers(resp);
-                    if(resp.complete) 
-                        location.href = oilsBasePath + '/acq/po/view/' + resp.purchase_order.id();
+                    if (resp.complete) {
+                        location.href = oilsBasePath + "/acq/po/view/" +
+                            resp.purchase_order.id();
+                    }
                 }
             }
         );
-    }
+    };
+
 
     this.batchFundWidget = null;
 
@@ -2419,39 +2520,59 @@ function AcqLiTable() {
     }
 
     this._savePl = function(values) {
-        var self = this;
-        var selected = this.getSelected( (values.which == 'all') );
-        openils.Util.show('acq-lit-generic-progress');
+        this.getSelected(
+            (values.which == 'all'),
+            function(list) { self._savePlFromLineitems(values, list); }
+        );
+    };
+
+    this._savePlFromLineitems = function(values, selected) {
+        openils.Util.show("acq-lit-generic-progress");
 
         if(values.new_name) {
             openils.acq.Picklist.create(
-                {name: values.new_name}, 
+                {name: values.new_name},
                 function(id) {
-                    self._updateLiList(id, selected, 0, 
-                        function(){
-                            location.href = oilsBasePath + '/acq/picklist/view/' + id;
-                        });
+                    self._updateLiList(
+                        id, selected, 0,
+                        function() {
+                            location.href =
+                                oilsBasePath + "/acq/picklist/view/" + id;
+                        }
+                    );
                 }
             );
         } else if(values.existing_pl) {
             // update lineitems to use an existing picklist
-            self._updateLiList(values.existing_pl, selected, 0, 
+            self._updateLiList(
+                values.existing_pl, selected, 0,
                 function(){
-                    location.href = oilsBasePath + '/acq/picklist/view/' + values.existing_pl;
-                });
+                    location.href =
+                        oilsBasePath + "/acq/picklist/view/" +
+                        values.existing_pl;
+                }
+            );
         }
-    }
+    };
 
     this._updateLiState = function(values, state) {
-        var self = this;
-        var selected = this.getSelected( (values.which == 'all') );
+        progressDialog.show(true);
+        this.getSelected(
+            (values.which == 'all'),
+            function(list) {
+                self._updateLiStateFromLineitems(values, state, list);
+            }
+        );
+    };
+
+    this._updateLiStateFromLineitems = function(values, state, selected) {
         if(!selected.length) return;
         dojo.forEach(selected, function(li) {li.state(state);});
-        self._updateLiList(null, selected, 0, 
+        self._updateLiList(null, selected, 0,
             // TODO consider inline updates for efficiency
             function() { location.href = location.href }
         );
-    }
+    };
 
     this._updateLiList = function(pl, list, idx, oncomplete) {
         if(idx >= list.length) return oncomplete();
index 53d942a..20d8a17 100644 (file)
@@ -4,6 +4,9 @@ function LiTablePager() {
     this.init = function(dataLoader, liTable, offset, limit) {
         this.dataLoader = dataLoader;
         this.liTable = liTable;
+        this.liTable.isUni = true;
+        this.liTable.pager = this;  /* XXX memory leak waiting to happen? */
+
         this.displayLimit = limit || 15;
         this.displayOffset = offset || 0;
 
@@ -57,5 +60,15 @@ function LiTablePager() {
         }
     };
 
+    this.getAllLineitemIDs = function(callback) {
+        this.dataLoader({
+            "id_list": true,
+            "atomic": true,
+            "skip_paging": true,
+            "onresponse": null,
+            "oncomplete": callback
+        });
+    };
+
     this.init.apply(this, arguments);
 }
index 53be741..8ff340e 100644 (file)
@@ -603,7 +603,6 @@ function ResultManager(liPager, poGrid, plGrid, invGrid) {
     var self = this;
 
     this.liPager = liPager;
-    this.liPager.liTable.isUni = true;
 
     this.poGrid = poGrid;
     this.plGrid = plGrid;
@@ -693,27 +692,55 @@ function ResultManager(liPager, poGrid, plGrid, invGrid) {
         }
     };
 
-    this._dataLoader = function() {
+    this._dataLoader = function(opts) {
         /* This function must contain references to "self" only, not "this." */
         var grid = self.result_types[self.result_type].interface;
+
+        if (!opts)
+            opts = {};
+
         self.count_results = 0;
-        self.params[4].offset = grid.displayOffset;
-        self.params[4].limit = grid.displayLimit;
-
-        fieldmapper.standardRequest(
-            ["open-ils.acq", self.method_name], {
-                "params": self.params,
-                "async": true,
-                "onresponse": function(r) {
-                    if (r = openils.Util.readResponse(r)) {
-                        if (!self.count_results++)
-                            self.show(self.result_type);
-                        self.add(self.result_type, r);
-                    }
-                },
-                "oncomplete": function() { self.resultsComplete(); }
-            }
-        );
+
+        var use_params = dojo.clone(self.params);   /* need copy, not ref */
+
+        if (!opts.skip_paging) {
+            use_params[4].offset = grid.displayOffset;
+            use_params[4].limit = grid.displayLimit;
+        }
+
+        var method = self.method_name;
+        if (opts.atomic)
+            method += ".atomic";
+
+        if (opts.id_list)
+            use_params[4].id_list = true;
+
+        var request_options = {
+            "params": use_params,
+            "async": true
+        };
+
+        if (typeof opts.onresponse != "undefined") {
+            request_options.onresponse = opts.onresponse;
+        } else {
+            /* normal onresponse handler for most times we call this method */
+            request_options.onresponse = function(r) {
+                if (r = openils.Util.readResponse(r)) {
+                    if (!self.count_results++)
+                        self.show(self.result_type);
+                    self.add(self.result_type, r);
+                }
+            };
+        }
+
+        if (typeof opts.oncomplete != "undefined") {
+            request_options.oncomplete = opts.oncomplete;
+        } else {
+            /* normal oncomplete handler for most times we call this method */
+            request_options.oncomplete = function() { self.resultsComplete(); };
+        }
+
+        fieldmapper.standardRequest(["open-ils.acq", method], request_options);
     };
 
     this.add = function(which, what) {
index 3fe41b8..cfbefca 100644 (file)
@@ -166,7 +166,9 @@ function load() {
         'ui.patron.edit.aua.post_code.example',
         'ui.patron.edit.aua.county.require',
         'format.date',
-        'ui.patron.edit.default_suggested'
+        'ui.patron.edit.default_suggested',
+        'opac.barcode_regex',
+        'opac.username_regex'
     ]);
 
     for(k in orgSettings)
@@ -987,6 +989,31 @@ function attachWidgetEvents(fmcls, fmfield, widget) {
         switch(fmfield) {
 
             case 'usrname':
+                widget.widget.isValid = function() {
+                    // No spaces
+                    if(this.attr("value").match(/\s/)) {
+                        return false;
+                    }
+                    // Can look like a barcode (for initial value)
+                    if(orgSettings['opac.barcode_regex']) {
+                        var test_regexp = new RegExp(orgSettings['opac.barcode_regex']);
+                        if(test_regexp.test(this.attr("value"))) {
+                            return true;
+                        }
+                    }
+                    // Can look like a username
+                    if(orgSettings['opac.username_regex']) {
+                        var test_regexp = new RegExp(orgSettings['opac.username_regex']);
+                        if(test_regexp.test(this.attr("value"))) {
+                            return true;
+                        }
+                    }
+                    // If we know what a barcode and username look like and we got here, reject
+                    if(orgSettings['opac.barcode_regex'] && orgSettings['opac.username_regex'])
+                        return false;
+                    // Otherwise we don't have enough info to say either way, let it through.
+                    return true;
+                }
                 dojo.connect(widget.widget, 'onChange', 
                     function() {
                         var input = findWidget('au', 'usrname');
index 1012c81..091d277 100644 (file)
@@ -100,7 +100,7 @@ function orgIsMine(me, org, depth) {
        if(me.id() == org.id()) {
                return true;
        }
-       if (depth) {
+       if (depth !== undefined) {
                while (depth < findOrgDepth(me)) {
                        me = findOrgUnit( me.parent_ou() );
                }
@@ -110,7 +110,7 @@ function orgIsMine(me, org, depth) {
        }
        var kids = me.children();
        for( var i = 0; kids && i < kids.length; i++ ) {
-               if(orgIsMine(kids[i], org, false)) {
+               if(orgIsMine(kids[i], org /* intentional lack of 3rd arg */)) {
                        return true;
                }
 
diff --git a/Open-ILS/web/opac/images/eg_tiny_logo.png b/Open-ILS/web/opac/images/eg_tiny_logo.png
new file mode 100644 (file)
index 0000000..c568f3e
Binary files /dev/null and b/Open-ILS/web/opac/images/eg_tiny_logo.png differ
diff --git a/Open-ILS/web/opac/images/main_logo.png b/Open-ILS/web/opac/images/main_logo.png
new file mode 100644 (file)
index 0000000..3ae9b35
Binary files /dev/null and b/Open-ILS/web/opac/images/main_logo.png differ
diff --git a/Open-ILS/web/opac/images/small_logo.png b/Open-ILS/web/opac/images/small_logo.png
new file mode 100644 (file)
index 0000000..47ef919
Binary files /dev/null and b/Open-ILS/web/opac/images/small_logo.png differ
index 69dba21..4714957 100644 (file)
 <!ENTITY ev.staff.patron.ue_xhtml.ue_addr_delete.label "Delete this Address">
 <!ENTITY ev.staff.patron.ue_xhtml.ue_addr_detach.label "Detach this Address">
 <!ENTITY ev.staff.patron.ue_xhtml.ue_addr_approve.label "Approve Pending Address">
-<!ENTITY ev.staff.patron.ue_xhtml.ue_addr_approve_confirm.label "Approve pending address?  This operation will be be instantenous.">
+<!ENTITY ev.staff.patron.ue_xhtml.ue_addr_approve_confirm.label "Approve pending address?  This operation will be be instantaneous.">
 <!ENTITY ev.staff.patron.ue_xhtml.primary_ident_ssn_help.label "(XXX-YY-ZZZZ)">
 <!ENTITY ev.staff.patron.ue_xhtml.primary_ident_dl_help.label "(GA-123456789)">
 <!ENTITY ev.staff.patron.ue_xhtml.edit.label "Edit">
index f1ea647..02e8231 100644 (file)
@@ -292,7 +292,7 @@ Please see a librarian to renew your account.">
 <!ENTITY myopac.summary.username.dup "The requested username is not available.  Please choose a different username.">
 <!ENTITY myopac.summary.username.success "Username successfully updated">
 <!ENTITY myopac.summary.username.failure "Username update failed">
-<!ENTITY myopac.summary.username.invalid "Username cannot contain spaces or have the same format as a barcode">
+<!ENTITY myopac.summary.username.invalid "Username cannot contain spaces or have the same format as a barcode, and may be restricted by policy">
 <!ENTITY myopac.summary.email.error "Please enter a valid email address">
 <!ENTITY myopac.summary.email.success "Email address successfully updated">
 <!ENTITY myopac.summary.email.failed "Email address update failed">
index 8e5c079..24caa23 100644 (file)
@@ -890,6 +890,22 @@ function _myOPACSummaryShowUer(r) {
        req.callback(myopacDrawNotes);
        req.send();
 
+    r = fetchOrgSettingDefault(G.user.home_ou(), 'opac.lock_usernames');
+    if(r) {
+        // No changing username - Policy Lock
+        hideMe($('myopac_summary_username_change'));
+    } else {
+        r = fetchOrgSettingDefault(G.user.home_ou(), 'opac.unlimit_usernames');
+        if(!r) {
+            r = fetchOrgSettingDefault(G.user.home_ou(), 'opac.barcode_regex');
+            if(r) REGEX_BARCODE = new RegExp(r);
+
+            if(!user.usrname().match(REGEX_BARCODE)) {
+                // No changing username - You already have one!
+                hideMe($('myopac_summary_username_change'));
+            }
+        }
+    }
 
        var tbody = $('myopac_addr_tbody');
        var template;
@@ -1057,6 +1073,7 @@ function myopacSaveAddress(row, addr, deleteMe) {
 
 function myOPACUpdateUsername() {
        var username = $('myopac_new_username').value;
+       var curpassword = $('myopac_username_current_password').value;
        if(username == null || username == "") {
                alert($('myopac_username_error').innerHTML);
                return;
@@ -1067,7 +1084,7 @@ function myOPACUpdateUsername() {
                return;
        }
 
-    r = fetchOrgSettingDefault(globalOrgTree.id(), 'opac.barcode_regex');
+    r = fetchOrgSettingDefault(G.user.home_ou(), 'opac.barcode_regex');
     if(r) REGEX_BARCODE = new RegExp(r);
 
     if(username.match(REGEX_BARCODE)) {
@@ -1075,6 +1092,14 @@ function myOPACUpdateUsername() {
         return;
     }
 
+    r = fetchOrgSettingDefault(G.user.home_ou(), 'opac.username_regex');
+    if(r) {
+        if(!username.match(new RegExp(r))) {
+            alert($('myopac_invalid_username').innerHTML);
+            return;
+        }
+    }
+
        /* first see if the requested username is taken */
        var req = new Request(CHECK_USERNAME, G.user.session, username);
        req.send(true);
@@ -1091,7 +1116,7 @@ function myOPACUpdateUsername() {
                return;
        }
 
-       var req = new Request(UPDATE_USERNAME, G.user.session, username );
+       var req = new Request(UPDATE_USERNAME, G.user.session, username, curpassword );
        req.send(true);
        if(req.result()) {
 
@@ -1115,12 +1140,13 @@ function myOPACUpdateUsername() {
 
 function myOPACUpdateEmail() {
        var email = $('myopac_new_email').value;
+       var curpassword = $('myopac_email_current_password').value;
        if(email == null || email == "") {
                alert($('myopac_email_error').innerHTML);
                return;
        }
 
-       var req = new Request(UPDATE_EMAIL, G.user.session, email );
+       var req = new Request(UPDATE_EMAIL, G.user.session, email, curpassword );
        req.send(true);
        if(req.result()) {
                G.user.email(email);
index eae2aa0..2d08344 100644 (file)
@@ -51,6 +51,9 @@ if(location.href.match(/&place_hold=1/)) {
     hideMe(dojo.byId('canvas_main'));
 }
 
+dojo.require("dijit.Dialog");
+dojo.require("dijit.form.TextBox");
+
 /* serials are currently the only use of Dojo strings in the OPAC */
 if (rdetailDisplaySerialHoldings) {
        dojo.require("dijit.Menu");
@@ -357,8 +360,9 @@ function _holdingsDraw(h) {
         // Only draw holdings within our OU scope
         var here = findOrgUnit(getLocation());
         var entryNum = 0;
+        var depth = getDepth();
         dojo.forEach(holdings, function (item) {
-            if (orgIsMine(here, findOrgUnit(item.owning_lib()))) {
+            if (orgIsMine(here, findOrgUnit(item.owning_lib()), depth)) {
                 _holdingsDrawMFHD(item, entryNum);
                 entryNum++;
             }
@@ -675,13 +679,23 @@ function rdetailAddBookbags(r) {
 }
 
 var _actions = {};
-function rdetailNewBookbag() {
-       var name = prompt($('rdetail_bb_new').innerHTML,"");
-       if(!name) return;
-
+/**
+ * Adds a new bookbag and exits.
+ * 
+ * exitstatus should be 0 if the status is to be read.
+ */
+function finishBookbag(exitstatus) {
+       var name = bbName.attr('value');
+       
+       newBBDialog.hide();     
+       bbName.attr("value", ""); // Do this after hide so the text doesn't disappear.
+       
+       if(exitstatus != 0) return; // If the user canceled, just drop off here.
+       
        var id;
+       
        if( id = containerCreate( name ) ) {
-               alert($('rdetail_bb_success').innerHTML);
+               alert( $('rdetail_bb_success').innerHTML );
                var selector = $('rdetail_more_actions_selector');
                insertSelectorVal( selector, nextContainerIndex++, name, 
                                "container_" + id, rdetailAddToBookbag, 1 );
@@ -689,6 +703,17 @@ function rdetailNewBookbag() {
        }
 }
 
+/**
+ * Creates a new Bookbag for the user.
+ */
+function rdetailNewBookbag() {
+    newBBDialog.show(); // Show the bookbag dialog.
+    dojo.connect(dijit.byId('newBBDialog'), 'onKeyPress', function(evt) {
+        if (evt.keyCode == dojo.keys.ENTER) {
+            finishBookbag(0);
+        }
+    });
+}
 
 function rdetailAddToBookbag() {
        var selector = $('rdetail_more_actions_selector');
index 40eda7f..821f870 100644 (file)
                                <td class='color_4 light_border'>&common.username;</td>
                                <td class='light_border' id='myopac_summary_username'> </td>
                                <td class='light_border'><a href='javascript:void(0);' 
-                                       onclick='unHideMe($("myopac_update_username_row"));$("myopac_new_username").focus();'
+                                       onclick='unHideMe($("myopac_update_username_row"));$("myopac_username_current_password").focus();'
                                        id='myopac_summary_username_change' style='text-decoration: underline;'>&myopac.summary.change;</a></td>
                        </tr>
 
                        <tr id='myopac_update_username_row' class='hide_me'>
                                <td class='myopac_update_cell' colspan='3'>
-                                       <span class='myopac_update_span'>&myopac.summary.username.enter; </span>
-                                       <input type='text' size='24' id='myopac_new_username'
-                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateUsername();' />
+
+                                       <table><tbody>
+                                               <tr>
+                                                       <td><span class='myopac_update_span'>&myopac.summary.password.current; </span></td>
+                                                       <td><input type='password' size='24' id='myopac_username_current_password'
+                                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateUsername();' /></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><span class='myopac_update_span'>&myopac.summary.username.enter; </span></td>
+                                                       <td><input type='text' size='24' id='myopac_new_username'
+                                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateUsername();' /></td>
+                                               </tr>
+                                       </tbody></table>
+
                                        <span class='myopac_update_span'>
                                                <button onclick='myOPACUpdateUsername();'>&common.submit;</button>
                                        </span>
                                <td class='color_4 light_border'>&myopac.summary.email;</td>
                                <td class='light_border' id='myopac_summary_email'> </td>
                                <td class='light_border'><a href='javascript:void(0);' 
-                                       onclick='unHideMe($("myopac_update_email_row"));$("myopac_new_email").focus();'
+                                       onclick='unHideMe($("myopac_update_email_row"));$("myopac_email_current_password").focus();'
                                        id='myopac_summary_email_change' style='text-decoration: underline;'>&myopac.summary.change;</a></td>
                        </tr>
 
                        <tr id='myopac_update_email_row' class='hide_me'>
                                <td class='myopac_update_cell' colspan='3'>
-                                       <span class='myopac_update_span'>&myopac.summary.email.new; </span>
-                                       <input type='text' size='24' id='myopac_new_email'
-                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateEmail();' />
+
+                                       <table><tbody>
+                                               <tr>
+                                                       <td><span class='myopac_update_span'>&myopac.summary.password.current; </span></td>
+                                                       <td><input type='password' size='24' id='myopac_email_current_password'
+                                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateEmail();' /></td>
+                                               </tr>
+                                               <tr>
+                                                       <td><span class='myopac_update_span'>&myopac.summary.email.new; </span></td>
+                                                       <td><input type='text' size='24' id='myopac_new_email'
+                                                               onkeydown='if(userPressedEnter(event)) myOPACUpdateEmail();' /></td>
+                                               </tr>
+                                       </tbody></table>
+
                                        <span class='myopac_update_span'>
                                                <button onclick='myOPACUpdateEmail();'>&common.submit;</button>
                                        </span>
index c816b6d..f676a72 100644 (file)
 
        <div class='hide_me' id='rdetail_bb_none'>&rdetail.none;</div>
        <div class='hide_me' id='rdetail_bb_item_success'>&rdetail.bookbag.add.success;</div>
-       <div class='hide_me' id='rdetail_bb_new'>&rdetail.bookbag.name;</div>
+       <div dojoType='dijit.Dialog' id='newBBDialog' jsId='newBBDialog' title='&rdetail.bookbag.name;' style="width: 20em;">
+        <input id='bbName' jsId='bbName' dojoType='dijit.form.TextBox'></input><br/>
+        <button id='bbok' dojoType='dijit.form.Button' onClick='finishBookbag(0);' type='button'>&common.submit;</button>
+        <button id='bbca' dojoType='dijit.form.Button' onClick='finishBookbag(1);' type='button'>&common.cancel;</button>
+    </div>
        <div class='hide_me' id='rdetail_bb_success'>&rdetail.bookbag.create.success;</div>
 
 </div>
index fdfb446..c4b023b 100644 (file)
@@ -71,7 +71,7 @@
             <button label="Inspect" oncommand="try { var dtb = document.getElementById('debug_tb'); var dx = eval( dtb.value ); var ds = ''; for (var di in dx) { ds += di + '=' + dx[di] + '\r\n'; }; window.open('data:text/plain;charset=UTF-8,'+window.escape(ds),'debug_win','chrome,resizable,modal'); } catch(E) { alert(E); }; dtb.focus();"/>
             <button label="js2JSON" oncommand="try { var dtb = document.getElementById('debug_tb'); alert( js2JSON( eval( dtb.value ) ) ); } catch(E) { alert(E); }; dtb.focus();"/>
         </hbox>
-        <keyset><key id="debug_box_key" keycode="VK_F7" modifiers="control,shift" oncommand="var dtb = document.getElementById('debug_tb'); var dx = document.getElementById('debug_box'); dx.hidden = !dx.hidden; if (!dx.hidden) dtb.focus();"/></keyset>
+        <keyset><key id="debug_box_key" keycode="VK_F7" modifiers="control,shift" oncommand="JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); var dtb = document.getElementById('debug_tb'); var dx = document.getElementById('debug_box'); if(data.enable_debug || !dx.hidden) { dx.hidden = !dx.hidden; if(!dx.hidden) dtb.focus();}"/></keyset>
  
         <script>dump('finished openils_util_overlay\n');</script>
     </scripts>
index 207469c..5b50799 100644 (file)
@@ -66,7 +66,7 @@
             <button label="Inspect" oncommand="try { var dtb = document.getElementById('debug_tb'); var dx = eval( dtb.value ); var ds = ''; for (var di in dx) { ds += di + '=' + dx[di] + '\r\n'; }; window.open('data:text/plain;charset=UTF-8,'+window.escape(ds),'debug_win','chrome,resizable,modal'); } catch(E) { alert(E); }; dtb.focus();"/>
             <button label="js2JSON" oncommand="try { var dtb = document.getElementById('debug_tb'); alert( js2JSON( eval( dtb.value ) ) ); } catch(E) { alert(E); }; dtb.focus();"/>
         </hbox>
-        <keyset><key id="debug_box_key" keycode="VK_F7" modifiers="control,shift" oncommand="var dtb = document.getElementById('debug_tb'); var dx = document.getElementById('debug_box'); dx.hidden = !dx.hidden; if (!dx.hidden) dtb.focus();"/></keyset>
+        <keyset><key id="debug_box_key" keycode="VK_F7" modifiers="control,shift" oncommand="JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); var dtb = document.getElementById('debug_tb'); var dx = document.getElementById('debug_box'); if(data.debug_build || data.enable_debug || !dx.hidden) { dx.hidden = !dx.hidden; if(!dx.hidden) dtb.focus();}"/></keyset>
  
     </scripts>
     <script>dump('Loaded OpenILS/util_overlay_offline.xul\n');</script>
index 35d885c..dd6de40 100644 (file)
@@ -456,6 +456,8 @@ auth.controller.prototype = {
                 this.on_login_error(E);
             }
         }
+        // Once we are done with it, clear the password
+        this.controller.view.password_prompt.value = '';
 
     },
 
@@ -551,8 +553,10 @@ auth.controller.prototype = {
         this.session.close();
         this.data.menu_perms = false;
         this.data.current_hotkeyset = undefined;
+        this.data.enable_debug = this.data.debug_client;
         this.data.stash('menu_perms');
         this.data.stash('current_hotkeyset');
+        this.data.stash('enable_debug');
 
         /* FIXME - need some locking or object destruction for the async tests */
         /* this.test_server( this.controller.view.server_prompt.value ); */
index ca5cb0d..c3f9a13 100644 (file)
@@ -60,6 +60,15 @@ auth.session.prototype = {
                         data.stash('ws_info');
                         data.ws_name = null; data.stash('ws_name');
                         params.type = 'temp';
+                        // We need to get a new seed
+                        init = this.network.request(
+                            api.AUTH_INIT.app,
+                            api.AUTH_INIT.method,
+                            [ this.view.name_prompt.value ]
+                        );
+                        if(init) {
+                            params.password = hex_md5(init + hex_md5( this.view.password_prompt.value ));
+                        }
                         robj = this.network.simple_request('AUTH_COMPLETE',[ params ]);
                         if (robj.ilsevent == 0) {
                             this.key = robj.payload.authtoken;
index 545d3a9..2609bb6 100644 (file)
@@ -16,16 +16,21 @@ Evergreen calculates due dates and loan periods. It determines how many renewals
 <blockquote>
 <a href="http://evergreen-ils.org/">http://evergreen-ils.org</a>
 </blockquote>
-<h2>The Evergreen Core Team:</h2>
+<h2>The Core Developers:</h2>
 <ul>
-<li><b>Shawn Boyette</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Developer<br/></li>
-<li><b>Bill Erickson</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Systems Developer<br/> </li>
-<li><b>Jason Etheridge</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Interface Developer<br/></li>
-<li><b>Brad LaJeunesse</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, System Administrator<br/></li>
-<li><b>Laura McFarland</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Interface Developer<br/></li>
-<li><b>Scott McKellar</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Developer<br/></li>
-<li><b>Mike Rylander</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a>, Database Developer<br/></li>
-<li><b>Dan Scott</b>, <a href="http://laurentian.ca">Laurentian University</a>, Developer<br/></li>
+<li><b>Joe Atzberger</b><br/></li>
+<li><b>Thomas Berezansky</b>, <a href="http://mvlc.org">Merrimack Valley Library Consortium</a><br/></li>
+<li><b>Shawn Boyette</b><br/></li>
+<li><b>Galen Charlton</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a><br></li>
+<li><b>Bill Erickson</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a><br/></li>
+<li><b>Jason Etheridge</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a><br/></li>
+<li><b>David Fiander</b><br/></li>
+<li><b>Lebbeous Fogle-Weekley</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a><br/></li>
+<li><b>Laura McFarland</b><br/></li>
+<li><b>Scott McKellar</b><br/></li>
+<li><b>Mike Rylander</b>, <a href="http://esilibrary.com">Equinox Software Inc.</a><br/></li>
+<li><b>Dan Scott</b>, <a href="http://laurentian.ca">Laurentian University</a><br/></li>
+<li><b>Dan Wells</b>, <a href="http://calvin.edu">Calvin College</a><br/></li>
 </ul>
 <h2>The Translation Team:</h2>
 <ul>
@@ -33,6 +38,7 @@ Evergreen calculates due dates and loan periods. It determines how many renewals
 <li><b>John Fink</b> (<a href="http://mcmaster.ca">McMaster University</a>) and <b><a href="http://thebookpile.wordpress.com/">Warren Layton</a></b>, en-CA<br/></li>
 <li><b><a href="http://nrcan.gc.ca/">Natural Resources Canada</a></b>, fr-CA<br/></li>
 <li><b>Tigran Zargaryan</b>, <a href="http://www.flib.sci.am">Fundamental Scientific Library of the National Academy of Sciences of the Republic of Armenia</a>, hy-AM<br/></li>
+<li>... and the many others who have contributed translations to <a href="http://translations.launchpad.net/evergreen">Evergreen translations on Launchpad</a></li>
 </ul>
 <p>The French (fr-CA) version of the contextual help for fields and subfields in the MARC editor was harvested from the MARC 21 Formats documentation hosted on the <a href="http://www.collectionscanada.gc.ca/index-e.html">Library and Archives Canada</a> Web site with the permission of Library and Archives Canada. This material is protected by copyright owned by Library and Archives Canada. This material is not an official version of the MARC 21 standard.</p>
 <h2>The Community:</h2>
index 123e3de..9598484 100644 (file)
@@ -212,6 +212,15 @@ function get_menu_perms(indocument) {
             var r = network.simple_request('BATCH_PERM_RETRIEVE_WORK_OU', [ G.data.session.key, get_menu_perms.perm_list ]);
             for(p in r)
                 r[p] = (typeof(r[p][0]) == 'number');
+            // Developer-enabled clients override permissions and always allow debugging
+            if(G.data.debug_build) {
+                r['DEBUG_CLIENT'] = true;
+            }
+            // If we have DEBUG_CLIENT (by force or otherwise) we can use debugging interfaces
+            // Doing this here because this function gets called at least once per login
+            // Including operator change
+            G.data.enable_debug = (r['DEBUG_CLIENT'] == true);
+            G.data.stash('enable_debug');
             G.data.menu_perms = r;
             G.data.stash('menu_perms');
         }
@@ -524,6 +533,12 @@ function main_init() {
         } catch(E) {
         }
 
+        // If we are showing the debugging frame then we consider this a debug build
+        // This could be a versionless build, a developer-pref enabled build, or otherwise
+        // If set this will enable all debugging commands, even if you normally don't have permission to use them
+        G.data.debug_build = !document.getElementById('debug_gb').hidden;
+        G.data.stash('debug_build');
+
         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] 
             .getService(Components.interfaces.nsIXULAppInfo); 
 
index a68b9bf..61d239c 100644 (file)
@@ -1585,6 +1585,46 @@ main.menu.prototype = {
                         xulG.pref.clearUserPref('open-ils.menu.toolbar.labelbelow');
                 }
             ],
+            'cmd_debug_venkman' : [
+                ['oncommand'],
+                function() {
+                    try{
+                        xulG.window.win.start_debugger();
+                    } catch(E) {
+                        alert(E);
+                    }
+                }
+            ],
+            'cmd_debug_inspector' : [
+                ['oncommand'],
+                function() {
+                    try{
+                        xulG.window.win.start_inspector();
+                    } catch(E) {
+                        alert(E);
+                    }
+                }
+            ],
+            'cmd_debug_chrome_list' : [
+                ['oncommand'],
+                function() {
+                    try{
+                        xulG.window.win.start_chrome_list();
+                    } catch(E) {
+                        alert(E);
+                    }
+                }
+            ],
+            'cmd_debug_chrome_shell' : [
+                ['oncommand'],
+                function() {
+                    try{
+                        xulG.window.win.start_js_shell();
+                    } catch(E) {
+                        alert(E)
+                    }
+                }
+            ],
         };
 
         JSAN.use('util.controller');
index 014ffab..4ec89a3 100644 (file)
     <command id="cmd_local_admin" />
     <command id="cmd_toggle_meters" />
 
-    <command id="cmd_extension_manager" />
-    <command id="cmd_theme_manager" />
-    <command id="cmd_about_config" />
+    <command id="cmd_extension_manager"
+             perm="DEBUG_CLIENT"
+             />
+    <command id="cmd_theme_manager"
+             perm="DEBUG_CLIENT"
+             />
+    <command id="cmd_about_config"
+             perm="DEBUG_CLIENT"
+             />
 
     <command id="cmd_adv_user_edit" />
-    <command id="cmd_console" />
-    <command id="cmd_shell" />
-    <command id="cmd_xuleditor" />
+    <command id="cmd_console"
+             perm="DEBUG_CLIENT" />
+    <command id="cmd_shell"
+             perm="DEBUG_CIENT"
+             />
+    <command id="cmd_xuleditor"
+             perm="DEBUG_CLIENT"
+             />
     <command id="cmd_fieldmapper" />
     <command id="cmd_test_html" />
     <command id="cmd_test_xul" />
     <command id="cmd_toolbar_mode_set" />
     <command id="cmd_toolbar_size_set" />
     <command id="cmd_toolbar_label_position_set" />
+    <command id="cmd_debug_venkman"
+             perm="DEBUG_CLIENT"
+             />
+    <command id="cmd_debug_inspector"
+             perm="DEBUG_CLIENT"
+             />
+    <command id="cmd_debug_chrome_list"
+             perm="DEBUG_CLIENT"
+             />
+    <command id="cmd_debug_chrome_shell"
+             perm="DEBUG_CLIENT"
+             />
 </commandset>
 
 <!-- The File menu on the main menu -->
                 <menuitem label="&staff.main.menu.admin.fieldmapper.label;" accesskey="&staff.main.menu.admin.fieldmapper.accesskey;" command="cmd_fieldmapper"/>
                 <menuitem label="&staff.main.menu.admin.cmd_console.label;" accesskey="&staff.main.menu.admin.cmd_console.accesskey;" command="cmd_console"/>
                 <menuitem label="&staff.main.menu.admin.cmd_shell.label;" accesskey="&staff.main.menu.admin.cmd_shell.accesskey;" command="cmd_shell"/>
-                <menuitem label="&staff.main.menu.admin.cmd_chrome_shell.label;" accesskey="&staff.main.menu.admin.cmd_chrome_shell.accesskey;" 
-                    oncommand="try{xulG.window.win.start_js_shell();}catch(E){alert(E);}"/>
+                <menuitem label="&staff.main.menu.admin.cmd_chrome_shell.label;" accesskey="&staff.main.menu.admin.cmd_chrome_shell.accesskey;" command="cmd_debug_chrome_shell"/> 
                 <menuitem label="server/main/test.html" accesskey="1" command="cmd_test_html"/>
                 <menuitem label="server/main/test.xul" accesskey="2" command="cmd_test_xul"/>
                 <menuitem label="&staff.main.menu.admin.clear_cache.label;" accesskey="&staff.main.menu.admin.clear_cache.accesskey;" command="cmd_clear_cache"/>
                 <menuitem label="&staff.main.menu.admin.extension_manager.label;" command="cmd_extension_manager"/>
                 <menuitem label="&staff.main.menu.admin.theme_manager.label;" command="cmd_theme_manager"/>
                 <menuitem label="&staff.main.menu.admin.about_config.label;" command="cmd_about_config"/>
-                <menuitem label="&staff.main.menu.admin.venkman.label;" oncommand="try{xulG.window.win.start_debugger();}catch(E){alert(E);}"/>
-                <menuitem label="&staff.main.auth.debug.inspector;" oncommand="try{xulG.window.win.start_inspector();}catch(E){alert(E);}"/>
-                <menuitem label="&staff.main.auth.debug.chrome_list;" oncommand="try{xulG.window.win.start_chrome_list();}catch(E){alert(E);}"/>
+                <menuitem label="&staff.main.menu.admin.venkman.label;" command="cmd_debug_venkman"/>
+                <menuitem label="&staff.main.auth.debug.inspector;" command="cmd_debug_inspector"/>
+                <menuitem label="&staff.main.auth.debug.chrome_list;" command="cmd_debug_chrome_list"/>
                 <menuitem label="&staff.main.menu.admin.ping;" oncommand="try{netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');JSAN.use('util.network'); var n = new util.network(); alert(n.ping());}catch(E){alert(E);}"/>
             </menupopup>
         </menu>
index a4222fa..49fa358 100644 (file)
@@ -155,20 +155,34 @@ util.print.prototype = {
 
             switch(content_type) {
                 case 'text/html' :
-                    var jsrc = 'data:text/javascript,' + window.escape('var params = window.arguments[0]; window.go_print = window.arguments[1];');
-                    var print_url = 'data:text/html,'
-                        + '<html id="top"><head><script src="/xul/server/main/JSAN.js"></script><script src="' + window.escape(jsrc) + '"></script></head>'
-                        + '<body onload="try{go_print();}catch(E){alert(E);}">' + window.escape(msg) + '</body></html>';
-                    w = obj.win.openDialog(print_url,'receipt_temp','chrome,resizable,minimizable', null, { "data" : params.data, "list" : params.list}, function() { 
+                    if(!params.type) {
+                        params.type = '';
+                    }
+                    var my_prefix = '/xul/server/';
+                    if(window.location.protocol == "chrome:") {
+        &