use OpenILS::Utils::Fieldmapper;
use OpenILS::Application::AppUtils;
use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Cache;
use Data::Dumper;
$Data::Dumper::Indent = 0;
my $U = 'OpenILS::Application::AppUtils';
my ($cgi) = @_;
return $cgi->param('query') unless $cgi->param('qtype');
-
+ $logger->info(Dumper($cgi->param('query')));
my %parts;
my @part_names = qw/qtype contains query bool/;
$parts{$_} = [ $cgi->param($_) ] for (@part_names);
$qtype = 'title';
$jtitle = 1;
}
-
# This stuff probably will need refined or rethought to better handle
# the weird things Real Users will surely type in.
$contains = "" unless defined $contains; # silence warning
return $full_query;
}
-sub _prepare_biblio_search {
+########################################################################
+# Replaces boolean search keywords [and, or, not] with the appropriate
+# tokens [&&, ||, -], and returns the modified query.
+# Keywords can be localised, localization files are located in
+# templates/opac/location
+########################################################################
+sub _prepare_biblio_search_boolean {
my ($cgi, $ctx) = @_;
+ my $cache = OpenSRF::Utils::Cache->new;
+ my $i18n;
+ my $location = $ctx->{locale};
+ #Reformat location abreveation [en_us -> en-US]
+ $location =~ s/(\w\w)_(\w\w)/$1\-\U$2\E/g;
+
+ #Check cache for localization valuse for the current locale
+ $i18n = $cache->get_cache("boolsearchlocale.${location}");
+ #unless there is a cached set of values load from disk and add to cache
+ unless (defined($i18n)){
+ #Else if the isn't: load values from disk and cache values
+ $i18n = {};
+ my $conf = OpenSRF::Utils::SettingsClient->new();
+ my $dir = $conf->config_value("dirs", "script");
+ my @foundFiles = glob("$dir/web/opac/locale/${location}/opac.dtd");
+ my $string_bundle = shift @foundFiles;
+ { #if file can not be opened we will just break out of this block
+ #open locale file or break out of the block
+ open(I18NFH, '<', $string_bundle) or last;
+ local $/ = undef;
+ my $file = <I18NFH>;
+ close(I18NFH);
+
+ #Regex for the keywords and, or, not
+ ( $i18n->{"AND"} ) = $file =~ /opac\.boolean\.keyword\.and "(.+)"/;
+ ( $i18n->{"OR"} ) = $file =~ /opac\.boolean\.keyword\.or "(.+)"/;
+ ( $i18n->{"NOT"} ) = $file =~ /opac\.boolean\.keyword\.not "(.+)"/;
+ }
+ #if AND keyword was not in opac.dtd or file couldn't be opened default to english
+ if(!defined($i18n->{"AND"}))
+ {
+ $logger->info("[ERROR] Boolean Search: AND keyword not found in opac.dtd, defaulting to english");
+ $i18n->{"AND"} = "and";
+ }
+ #if OR keyword was not in opac.dtd or file couldn't be opened default to english
+ if(!defined($i18n->{"OR"}))
+ {
+ $logger->info("[ERROR] Boolean Search: OR keyword not found in opac.dtd, defaulting to english");
+ $i18n->{"OR"} = "or";
+ }
+ #if NOT keyword was not in opac.dtd or file couldn't be opened default to english
+ if(!defined($i18n->{"NOT"}))
+ {
+ $logger->info("[ERROR] Boolean Search: NOT keyword not found in opac.dtd, defaulting to english");
+ $i18n->{"NOT"} = "not";
+ }
+ #add localization keywords to cache
+ $cache->put_cache("boolsearchlocale.${location}", $i18n);
+ }
+
+ my $and = $i18n->{"AND"};
+ my $or = $i18n->{"OR"};
+ my $not = $i18n->{"NOT"};
+ #replace keywords in the submited query with the boolean tokens
+ my $queryStatement = $cgi->param('boolean_query');
+ #RegEx selects the l18n keyword in the query and substatutes in '&&'
+ $queryStatement =~ s/((?<=" )(\b$and\b)|(\b$and\b)((?= ")|(?=(\w|\s)+((\b($and|$or|$not)\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\&\&/gi;
+ #RegEx selects the l18n keyword in the query and substatutes in '||'
+ $queryStatement =~ s/((?<=" )(\b$or\b)|(\b$or\b)((?= ")|(?=(\w|\s)+((\b($and|$or|$not})\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\|\|/gi;
+ #RegEx selects the l18n keyword and all trailing white space in the query and substatutes in '-'
+ $queryStatement =~ s/((?<=" )(\b$not\s+)|(\b$not\s+)((?= ")|(?=(\w|\s)+((\b($and|$or|$not)\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\-/gi;
+ $ctx->{query} = $queryStatement;
+ return $queryStatement;
+}
- my $query = _prepare_biblio_search_basics($cgi) || '';
+sub _prepare_biblio_search {
+ my ($cgi, $ctx) = @_;
+ my $query;
+ if ($cgi->param('boolean')) {
+ $query = _prepare_biblio_search_boolean($cgi,$ctx);
+ }else {
+ $query = _prepare_biblio_search_basics($cgi) || '';
+ }
$query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
-
foreach ($cgi->param('modifier')) {
# The unless bit is to avoid stacking modifiers.
$query = ('#' . $_ . ' ' . $query) unless
}
my ($query, $site, $depth) = _prepare_biblio_search($cgi, $ctx);
-
$self->get_staff_search_settings;
if (!$find_last and $ctx->{staff_saved_search_size}) {
<a href="[% mkurl('', {pane => 'expert'}) %]"
[% IF pane == 'expert' %]class="on" [% END %]
id="expert_search">[% l('Expert Search') %]</a>
+ [% IF search.boolean_enabled == "true" %]
+ <a href="[% mkurl('', {pane => 'boolean'}) %]"
+ [% IF pane == 'boolean' %]class="on" [% END %]
+ id="boolean_search">[% l('Boolean Search') %]</a>
+ [% END %]
</div>
</div>
</div>
[% INCLUDE "opac/parts/advanced/numeric.tt2" %]
[% ELSIF pane == 'expert' %]
[% INCLUDE "opac/parts/advanced/expert.tt2" %]
+ [% ELSIF pane == 'boolean' %]
+ [% INCLUDE "opac/parts/advanced/boolean.tt2" %]
[% END %]
</div>
<div class="common-full-pad"></div>
padding-left: 10px !important;
}
-#adv_search.on, #num_search.on, #expert_search.on {
+#adv_search.on, #num_search.on, #expert_search.on, #boolean_search.on {
color: [% css_colors.accent_darker %];
background: [% css_colors.background %];
text-decoration: none;
--- /dev/null
+[% query = CGI.param('boolean_query'); %]
+<form action="[% ctx.opac_root %]/results" method="GET">
+ <div style="width:100%;" class="header_middle">[% l("Free-Form Boolean Search") %]</div>
+ <input class="hidden" name="boolean" class="hidden" value=true/>
+ <input type="text" name="boolean_query" size="50" value="[% query | html %]" autofocus />
+ <div style="width:100%;" class="header_middle">[% l('Search Filters') %]</div>
+ <table cellpadding='10' cellspacing='0' border='0'>
+ [%
+ in_row = 0;
+ FOR adv_chunk IN search.bool_config;
+ NEXT IF adv_chunk.adv_hide;
+ IF in_row == 0;
+ in_row = 1; %]
+ <tr>
+ [%
+ END; %]
+ <td valign='top'>
+ <strong>[% adv_chunk.adv_label %]</strong><br />
+ [%
+ IF adv_chunk.adv_special;
+ SWITCH adv_chunk.adv_special;
+ CASE "lib_selector";
+ PROCESS "opac/parts/org_selector.tt2";
+ INCLUDE build_org_selector show_loc_groups=1; %]
+ <div style="position:relative;top:7px;">
+ <input type='checkbox' name="modifier"
+ value="available"[% CGI.param('modifier').grep('available').size ? ' checked="checked"' : '' %]
+ id='opac.result.limit2avail' />
+ <label style="position:relative;top:-2px;"
+ for='opac.result.limit2avail'>
+ [% l("Limit to Available") %]</label>
+ </div>
+ [%
+ CASE "pub_year"; %]
+ <select name='pubdate' onchange='
+ if(this.selectedIndex == 3)
+ unHideMe($("adv_global_pub_date_2_span"));
+ else
+ hideMe($("adv_global_pub_date_2_span"));'>
+ [% FOR opt IN [
+ {"code" => "is", "label" => l("Is")},
+ {"code" => "before", "label" => l("Before")},
+ {"code" => "after", "label" => l("After")},
+ {"code" => "between", "label" => l("Between")} ] %]
+ <option value="[% opt.code %]"[% CGI.param('pubdate') == opt.code ? ' selected="selected"' : '' %]>[% opt.label | html %]</option>
+ [% END %]
+ </select>
+ <div style='margin-top:5px;'>
+ <input name='date1' type='text' size='4' maxlength='4' value="[% CGI.param('date1') | html %]" />
+ <span id='adv_global_pub_date_2_span' class='[% CGI.param("pubdate") == "between" ? "" : "hide_me" %]'>
+ [% l("and") %] <input name='date2' type='text' size='4' maxlength='4' value="[% CGI.param('date2') | html %]" />
+ </span>
+ </div>
+ [%
+ CASE "sort_selector";
+ INCLUDE "opac/parts/filtersort.tt2"
+ value=CGI.param('sort') class='results_header_sel';
+ END;
+ ELSIF adv_chunk.adv_attr;
+ INCLUDE "opac/parts/coded_value_selector.tt2"
+ attr=adv_chunk.adv_attr multiple="multiple" size="4";
+ END; %]
+ </td>
+ [%
+ IF adv_chunk.adv_break;
+ in_row = 0; %]
+ </tr>
+ [%
+ END;
+ END; %]
+ </table>
+ <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>
+ <a href="[% mkurl(ctx.opac_root _ '/advanced', {$loc_name => loc_value, pane => 'boolean'}, 1) %]"
+ class="pointer opac-button">[% l('Clear Form') %]</a>
+</form>
\ No newline at end of file
#search.default_adv_select_height = 4;
##############################################################################
+# Define the boolean search limiters and labels.
+# adv_label is the (translated) label for the limiter
+# adv_attr is an array of possible limiters, the first one that has any
+# values will be used
+# adv_break will end the current row. If specified with a label/attr it
+# will do so *after* that limiter.
+# adv_special will drop in a special entry:
+# sort_selector will put the sort results selector
+# lib_selector will put the search library box (with limit to available)
+# pub_year will put the publication year box
+
+search.bool_config = [
+ {adv_label => l("Item Type"), adv_attr => ["mattype", "item_type"]},
+ {adv_label => l("Language"), adv_attr => "item_lang"},
+ {adv_label => l("Sort Results"), adv_special => "sort_selector", adv_break => 1},
+ {adv_label => l("Search Library"), adv_special => "lib_selector"},
+ {adv_label => l("Publication Year"), adv_special => "pub_year"},
+];
+
+##############################################################################
+# Define if the boolean search tab is enabled on the
+# advanced search page
+
+search.boolean_enabled = "true";
+
+##############################################################################
# For each search box the default "query type" value can be specified here
# This is the actual backend value, not the label
# Also note that including more than the row count entries won't add rows
is_advanced = CGI.param("_adv").size;
is_special = CGI.param("_special").size;
+ is_boolean = CGI.param("boolean").size;
# Check if we want to show the detail record view. Doing this
# here because we don't want to repeat logic in multiple other
<label id="search_box_label" for="search_box">[% l('Search: ') %]
<input type="text" id="search_box" name="query" aria-label="[%
l('Enter search query:');
- %]" value="[% is_advanced ? ctx.naive_query_scrub(ctx.processed_search_query) : CGI.param('query') | html %]"
+ %]" value="[% IF is_boolean; -%]
+ [%- ctx.query | html; -%]
+ [%- END; -%]
+ [%- is_advanced ? ctx.naive_query_scrub(ctx.processed_search_query) : CGI.param('query') | html %]"
[%- IF use_autosuggest.enabled == "t" %]
dojoType="openils.widget.AutoSuggest" type_selector="'qtype'"
submitter="this.textbox.form.submit();"
%]</a> ]
</div>
[% END %]
+ [% IF (is_boolean) %]
+ <div class="opac-auto-102">
+ [ <a href="[% mkurl(ctx.opac_root _ '/advanced', {$loc_name => loc_value, pane => 'boolean'}) %]">[%
+ l('Refine My Original Search')
+ %]</a> ]
+ </div>
+ [% END %]
<!--
<div id="breadcrumb">
<a href="[% ctx.opac_root %]/home">[% l('Catalog Home') %]</a> >
<!ENTITY opac.advanced.copy_loc_filter "Shelving Location">
<!-- ==========================================================
+ Boolean Search Tab
+ ======================================================= -->
+<!ENTITY opac.boolean.keyword.and "and">
+<!ENTITY opac.boolean.keyword.or "or">
+<!ENTITY opac.boolean.keyword.not "not">
+
+<!-- ==========================================================
MARC expert search
========================================================== -->
<!ENTITY search.marc "MARC Expert Search">