1 package OpenILS::WWW::EGCatLoader;
2 use strict; use warnings;
3 use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_GONE HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST HTTP_NOT_FOUND);
4 use OpenSRF::Utils::Logger qw/$logger/;
5 use OpenILS::Utils::CStoreEditor qw/:funcs/;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenILS::Application::AppUtils;
10 my $U = 'OpenILS::Application::AppUtils';
16 $ctx->{page} = 'course';
17 $ctx->{readonly} = $self->cgi->param('readonly');
19 my $course_id = $ctx->{page_args}->[0];
21 return Apache2::Const::HTTP_BAD_REQUEST
22 unless $course_id and $course_id =~ /^\d+$/;
24 $ctx->{course} = $U->simplereq(
26 'open-ils.courses.courses.retrieve',
30 $ctx->{instructors} = $U->simplereq(
32 'open-ils.courses.course_users.retrieve',
36 $ctx->{course_materials} = $U->simplereq(
38 'open-ils.courses.course_materials.retrieve.fleshed.atomic',
39 {course => $course_id}
41 return Apache2::Const::OK;
44 sub load_course_browse {
48 my $e = $self->editor;
50 my $browse_results = [];
52 # Are we searching? Cool, let's generate some links
53 if ($cgi->param('bterm')) {
54 my $bterm = $cgi->param('bterm');
55 my $qtype = $cgi->param('qtype');
56 my $locg = $cgi->param('locg');
57 my $pivot = scalar($self->cgi->param('bpivot'));
59 $self->cgi->param('blimit') ||
60 $self->ctx->{opac_hits_per_page} || 10
62 # Search term is optional. If it's empty, start at the
63 # beginning. Otherwise, center results on a match.
64 # Regardless, we're listing everything, so retrieve all.
67 if ($qtype eq 'instructor') {
68 $instructors = $e->json_query({
70 "select" => {"acmcu" => [
74 # TODO: We need to support the chosen library as well...
75 "where" => {'usr_role' => {'in' => {'from' => 'acmr', 'select' => {'acmr' => ['id']}, 'where' => {'+acmr' => 'is_public'}}}}
77 $results = $e->json_query({
79 "select" => {"au" => [
81 'pref_first_given_name',
83 'pref_second_given_name',
88 "order_by" => {'au' => ['pref_family_name', 'family_name']},
89 "where" => {'-and' => [{
95 "where" => {'-and' => [
96 {'usr_role' => { 'in' => {
101 "where" => {'+acmr' => 'is_public'}}}},
102 {"course" => { "in" =>{
107 "where" => {'-not' => [{'+acmc' => 'is_archived'}]}
114 $results = $e->json_query({
116 "select" => {"acmc" => [
123 "order_by" => {"acmc" => [$qtype]},
125 '-not' => {'+acmc' => 'is_archived'},
126 '+acmc' => { 'owning_lib' => $U->get_org_descendants($locg) }
132 for my $result(@$results) {
133 my $value_exists = 0;
137 'results_count' => 0,
141 if ($qtype eq 'instructor') {
142 # Put together the name
144 if ($result->{'pref_family_name'}) {
145 $name_str = $result->{'pref_family_name'} . ", ";
146 } elsif ($result->{'family_name'}) {
147 $name_str = $result->{'family_name'} . ", ";
150 if ($result->{'pref_first_given_name'}) {
151 $name_str .= $result->{'pref_first_given_name'};
152 } elsif ($result->{'first_given_name'}) {
153 $name_str .= $result->{'first_given_name'};
156 if ($result->{'pref_second_given_name'}) {
157 $name_str .= " " . $result->{'pref_second_given_name'};
158 } elsif ($result->{'second_given_name'}) {
159 $name_str .= " " . $result->{'second_given_name'};
162 $result->{$rqtype} = $name_str;
164 # Get an accurate count of matching courses
165 for my $instructor(@$instructors) {
166 if ($instructor->{'usr'} eq $result->{'id'}) {
167 $entry->{'results_count'} += 1;
172 $entry->{'results_count'} += 1;
175 for my $existing_entry(@$browse_results) {
176 if ($existing_entry->{'value'} eq $result->{$rqtype} && $value_exists eq 0) {
178 $existing_entry->{'results_count'} += 1;
183 if ($value_exists eq 0) {
184 # For Name/Course Number browse queries...
185 if ($bterm_match eq 0) {
186 if ($result->{$rqtype} =~ m/^$bterm/i || $result->{$rqtype} eq m/^$bterm/i || $result->{$rqtype} =~ m/^$bterm./i || $result->{$rqtype} eq m/^$bterm./i) {
188 $entry->{'match'} = 1;
191 $entry->{'value'} = $result->{$rqtype};
192 push @$browse_results, $entry;
195 # Feels a bit hacky, but we need the index of the matching entry
198 for my $i (0..$#$browse_results) {
199 if ($browse_results->[$i]->{'match'}) {
206 for my $i(0..$#$browse_results) {
207 $browse_results->[$i]->{'browse_index'} = $i;
209 $ctx->{match_idx} = $match_idx;
210 $ctx->{browse_results} = $browse_results;
213 return Apache2::Const::OK;
219 my $internal = $args{internal};
220 my $cgi = $self->cgi;
221 my $ctx = $self->ctx;
222 my $e = $self->editor;
225 $ctx->{page} = 'cresult' unless $internal;
227 $ctx->{courses} = [];
228 $ctx->{hit_count} = 0;
229 $ctx->{search_ou} = $self->_get_search_lib();
230 my $page = $cgi->param('page') || 0;
231 my $offset = $page * $limit;
233 $ctx->{page_size} = $limit;
234 $ctx->{search_page} = $page;
235 $ctx->{pagable_limit} = 50;
237 # fetch this page plus the first hit from the next page
239 $limit = $offset + $limit + 1;
243 my ($user_query, $query, @queries, $modifiers) = _prepare_course_search($cgi, $ctx);
245 #return Apache2::Const::OK unless $query;
247 $ctx->{user_query} = $user_query;
248 $ctx->{processed_search_query} = $query;
249 my $search_args = {};
250 my $course_numbers = ();
251 # Handle is_archived checkbox and Org Selector
252 my $search_orgs = $U->get_org_descendants($ctx->{search_ou});
254 my $where_clause = _create_where_clause($ctx, \@queries, $search_orgs);
256 my $hits = $e->json_query({
258 "select" => {"acmc" => ['id']},
259 "where" => $where_clause
263 $results = $e->json_query({
265 "select" => {"acmc" => [
275 "order_by" => {"acmc" => ['id']},
276 "where" => $where_clause
278 for my $result (@$results) {
279 push @{$ctx->{courses}}, {
281 course_number => $result->{course_number},
282 section_number => $result->{section_number},
283 owning_lib => $result->{owning_lib},
284 name => $result->{name},
285 is_archived => $result->{is_archived},
290 #$ctx->{courses} = $@courses;#[{id=>10, name=>"test", course_number=>"LIT"}];
291 $ctx->{hit_count} = @$hits || 0;
292 #$ctx->{hit_count} = 0;
293 return Apache2::Const::OK;
296 sub _create_where_clause {
297 my ($ctx, $queries, $search_orgs) = @_;
303 push @$and_terms, {'owning_lib' => $search_orgs};
304 push @$and_terms, {'-not' => {'+acmc' => 'is_archived'}} unless $ctx->{processed_search_query} =~ qr\#include_archived\;
306 # Now let's push the actual queries
307 for my $query_obj (@{$queries}) {
308 my $type = $query_obj->{'qtype'};
309 my $query = $query_obj->{'value'};
311 # Remove punctuation except for the the * wildcard
312 $query =~ s/\w\s\*//;
314 # Do we have a blank query, or just wildcards and/or spaces?
315 next if $query =~ /^[\s\*]*$/;
316 my $bool = $query_obj->{'bool'};
317 my $contains = $query_obj->{'contains'};
318 my $operator = ($contains eq 'nocontains') ? '!~*' : '~*';
320 if ($type eq 'instructor') {
321 $query =~ s/\*$/:*/; # postgres prefix matching syntax ends with :* (e.g. string:*, not string*)
322 $query =~ s/^\s+|\s+$//g; # preceding and trailing spaces have the potential to make to_tsquery mad
323 $query =~ s/\s/ \& /g; # postgres to_tsquery wants & characters between words
324 my $in = ($contains eq 'nocontains') ? "not in" : "in";
325 $search_query = {'id' => {$in => {
326 'from' => {'acmcu' => 'acmr'},
327 'select' => {'acmcu' => ['course']},
329 {"+acmr"=>"is_public"},
331 {"select"=>{"au"=>["id"]},
332 "where"=>{"name_kw_tsvector"=>
333 {"@@"=>{"value"=>["to_tsquery",$query]}}},
338 $search_query = ($contains eq 'nocontains') ?
339 {'+acmc' => { $type => {$operator => $query}}} :
340 {$type => {$operator => $query}};
344 push @$or_terms, $search_query;
347 if ($bool eq 'and') {
348 push @$and_terms, $search_query;
352 if ($or_terms and @$or_terms > 0) {
353 if ($and_terms and @$and_terms > 0) {
354 push @$or_terms, $and_terms;
356 $where_clause = {'-or' => $or_terms};
358 $where_clause = {'-and' => $and_terms};
360 return $where_clause;
363 sub _prepare_course_search {
364 my ($cgi, $ctx) = @_;
366 my ($user_query, @queries) = _prepare_query($cgi);
370 my $query = $user_query;
371 $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
373 foreach ($cgi->param('modifier')) {
374 $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
378 foreach (grep /^fi:/, $cgi->param) {
380 my $term = join(",", $cgi->param($_));
381 $query .= " $1($term)" if length $term;
384 #return () unless $query;
386 return ($user_query, $query, @queries);
392 return $cgi->param('query') unless $cgi->param('qtype');
395 my @part_names = qw/qtype contains query bool modifier/;
396 $parts{$_} = [ $cgi->param($_) ] for (@part_names);
400 for (my $i = 0; $i < scalar @{$parts{'qtype'}}; $i++) {
401 my ($qtype, $contains, $query, $bool, $modifier) = map { $parts{$_}->[$i] } @part_names;
402 next unless $query =~ /\S/;
404 $contains = "" unless defined $contains;
407 contains => $contains,
413 $bool = ($bool and $bool eq 'or') ? '||' : '&&';
415 $query = "$qtype:$query";
417 $full_query = $full_query ? "($full_query $bool $query)" : $query;
420 return ($full_query, @queries);