1 package OpenILS::WWW::AutoSuggest;
7 use Apache2::Const -compile => qw(
8 OK HTTP_NOT_ACCEPTABLE HTTP_INTERNAL_SERVER_ERROR :log
12 use CGI qw(:all -utf8);
13 use Digest::MD5 qw(md5_hex);
15 use OpenSRF::Utils::JSON;
16 use OpenILS::Utils::CStoreEditor qw/:funcs/;
17 use OpenSRF::Utils::Logger qw/:level/;
18 use OpenSRF::Utils::SettingsClient;
19 use OpenSRF::Utils::Cache;
21 my $log = 'OpenSRF::Utils::Logger';
29 my $conf = OpenSRF::Utils::SettingsClient->new;
31 $cache_timeout = $conf->config_value(
32 "apps", "open-ils.search", "app_settings", "cache_timeout" ) || 300;
36 $cache = OpenSRF::Utils::Cache->new('global');
38 return Apache2::Const::OK;
41 # BEGIN package globals
43 # We'll probably never need this fanciness for autosuggest, but
44 # you can add handlers for different requested content-types here, and
45 # you can weight them to control what matches requests for things like
48 my $_output_handler_dispatch = {
49 "application/xml" => {
53 $r->content_type("application/xml; charset=utf-8");
54 print suggestions_to_xml($data);
55 return Apache2::Const::OK;
58 "application/json" => {
62 $r->content_type("application/json; charset=utf-8");
63 print suggestions_to_json($data);
64 return Apache2::Const::OK;
69 my @_output_handler_types = sort {
70 $_output_handler_dispatch->{$a}->{prio} <=>
71 $_output_handler_dispatch->{$b}->{prio}
72 } keys %$_output_handler_dispatch;
76 # The third argument to our stored procedure, metabib.suggest_browse_entries(),
77 # is passed through directly to ts_headline() as the 'options' arugment.
78 sub prepare_headline_opts {
79 my ($css_prefix, $highlight_min, $highlight_max, $short_word_length) = @_;
81 $css_prefix =~ s/[^\w]//g;
84 qq{StartSel="<span class='$css_prefix'>"},
88 push @parts, "MinWords=$highlight_min" if $highlight_min > 0;
89 push @parts, "MaxWords=$highlight_max" if $highlight_max > 0;
90 push @parts, "ShortWord=$short_word_length" if defined $short_word_length;
92 return join(", ", @parts);
95 # Get raw autosuggest data (rows returned from a stored procedure) from the DB.
98 my $query = shift || ""; # avoid noise about undef
99 my $search_class = shift || "";
100 my $org_unit = shift || -1;
101 my $css_prefix = shift || 'oils_AS';
102 my $highlight_min = int(shift || 0);
103 my $highlight_max = int(shift || 0);
104 my $short_word_length = shift;
106 my $normalization = int(shift || 14); # 14 is not totally arbitrary.
107 # See http://www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING
109 my $limit = int(shift || 10);
111 $limit = 10 unless $limit > 0;
113 my $headline_opts = prepare_headline_opts(
114 $css_prefix, $highlight_min, $highlight_max,
115 defined $short_word_length ? int($short_word_length) : undef
118 my $key = 'oils_AS_' . md5_hex(
127 my $res = $cache->get_cache( $key );
129 return $res if ($res);
131 $res = $editor->json_query({
133 "metabib.suggest_browse_entries",
143 $cache->put_cache( $key => $res => $cache_timeout );
148 sub suggestions_to_xml {
149 my ($suggestions) = @_;
151 my $dom = new XML::LibXML::Document("1.0", "UTF-8");
152 my $as = $dom->createElement("as");
153 $dom->setDocumentElement($as);
155 foreach (@$suggestions) {
156 my $val = $dom->createElement("val");
157 $val->setAttribute("term", $_->{value});
158 $val->setAttribute("field", $_->{field});
159 $val->appendText($_->{match});
163 # XML::LibXML::Document::toString() returns an encoded byte string, which
164 # is why we don't need to binmode STDOUT, ':utf8'.
165 return $dom->toString();
168 sub suggestions_to_json {
169 my ($suggestions) = @_;
171 return OpenSRF::Utils::JSON->perl2JSON({
174 +{ term => $_->{value}, field => $_->{field},
175 match => $_->{match} }
181 # Given data and the Apache request object, this sub picks a sub from a
182 # dispatch table based on the list of content-type encodings that the client
183 # has indicated it will accept, and calls that sub, which will deliver
184 # a response of appropriately encoded data.
188 foreach my $media_range (split /,/, $r->headers_in->{Accept}) {
189 $media_range =~ s/;.+$//; # keep type, subtype. lose parameters.
192 Text::Glob::match_glob($media_range, $_)
193 } @_output_handler_types;
196 return $_output_handler_dispatch->{$match}{code}->($r, $data);
200 return Apache2::Const::HTTP_NOT_ACCEPTABLE;
204 child_init() unless $init_done;
209 my $editor = new_editor;
210 my $suggestions = get_suggestions(
212 map { scalar($cgi->param($_)) } qw(
225 if (not $suggestions) {
227 "get_suggestions() failed: " . $editor->die_event->{textcode}
229 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
234 return output_handler($r, $suggestions);