From: Kyle Huckins Date: Mon, 9 Dec 2019 00:01:44 +0000 (+0000) Subject: OPAC Course Search Page X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=refs%2Fheads%2Fuser%2Fkhuckins%2Flp1849212-course-materials-module-omnibus;p=working%2FEvergreen.git OPAC Course Search Page - Implement Course Search OPAC page, based on Advanced Catalog Search - Edit Searchbar to include entry for Course Search Signed-off-by: Kyle Huckins Changes to be committed: modified: Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm modified: Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm modified: Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm new file: Open-ILS/src/templates/opac/course/results.tt2 new file: Open-ILS/src/templates/opac/course_search.tt2 modified: Open-ILS/src/templates/opac/css/style.css.tt2 new file: Open-ILS/src/templates/opac/parts/course_search/global_row.tt2 new file: Open-ILS/src/templates/opac/parts/course_search/qtype_selector.tt2 modified: Open-ILS/src/templates/opac/parts/searchbar.tt2 --- diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm index 3c6c35cb71..b3e1361665 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm @@ -1062,15 +1062,6 @@ __PACKAGE__->register_method( @params args : Supplied object to filter search. /); -__PACKAGE__->register_method( - method => 'fetch_courses', - autoritative => 1, - api_name => 'open-ils.circ.courses.retrieve', - signature => q/ - Returns an array of course materials. - @params course_id: The id of the course we want to retrieve - /); - sub fetch_course_materials { my ($self, $conn, $args) = @_; my $e = new_editor(); @@ -1125,6 +1116,15 @@ sub fetch_course_materials { return $targets; } +__PACKAGE__->register_method( + method => 'fetch_courses', + autoritative => 1, + api_name => 'open-ils.circ.courses.retrieve', + signature => q/ + Returns an array of course materials. + @params course_id: The id of the course we want to retrieve + /); + sub fetch_courses { my ($self, $conn, @course_ids) = @_; my $e = new_editor(); @@ -1168,7 +1168,7 @@ sub fetch_course_users { unless ($self->api_name =~ /\.staff/) and $e->allowed('MANAGE_RESERVES'); - $users->{list} = $e->search_asset_course_module_course_users($filter, {order_by => {acmcu => 'course'}}); + $users->{list} = $e->search_asset_course_module_course_users($filter, {order_by => {acmcu => 'id'}}); for my $course_user (@{$users->{list}}) { my $patron = {}; $patron->{id} = $course_user->id; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm index 35f372afcb..bb415d8116 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -141,6 +141,8 @@ sub load { return $self->load_simple("home") if $path =~ m|opac/home|; return $self->load_simple("css") if $path =~ m|opac/css|; + return $self->load_cresults if $path =~ m|opac/course/results|; + return $self->load_simple("course_search") if $path =~ m|opac/course_search|; return $self->load_simple("advanced") if $path =~ m:opac/(advanced|numeric|expert):; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm index bd340eadfb..496c043f3e 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm @@ -39,4 +39,200 @@ sub load_course { {course => $course_id} ); return Apache2::Const::OK; +} + +sub load_cresults { + my $self = shift; + my %args = @_; + my $internal = $args{internal}; + my $cgi = $self->cgi; + my $ctx = $self->ctx; + my $e = $self->editor; + my $limit = 10; + + $ctx->{page} = 'cresult' unless $internal; + $ctx->{ids} = []; + $ctx->{courses} = []; + $ctx->{hit_count} = 0; + $ctx->{search_ou} = $self->_get_search_lib(); + my $page = $cgi->param('page') || 0; + my $offset = $page * $limit; + my $results; + $ctx->{page_size} = $limit; + $ctx->{search_page} = $page; + $ctx->{pagable_limit} = 50; + + # fetch this page plus the first hit from the next page + if ($internal) { + $limit = $offset + $limit + 1; + $offset = 0; + } + + my ($user_query, $query, @queries, $modifiers) = _prepare_course_search($cgi, $ctx); + + return Apache2::Const::OK unless $query; + + $ctx->{user_query} = $user_query; + $ctx->{processed_search_query} = $query; + my $search_args = {}; + my $course_numbers = (); + + my $where_clause; + my $and_terms = []; + my $or_terms = []; + + # Handle is_archived checkbox and Org Selector + my $search_orgs = $U->get_org_descendants($ctx->{search_ou}); + push @$and_terms, {'owning_lib' => $search_orgs}; + push @$and_terms, {'-not' => {'+acmc' => 'is_archived'}} unless $query =~ qr\#include_archived\; + + # Now let's push the actual queries + for my $query_obj (@queries) { + my $type = $query_obj->{'qtype'}; + my $query = $query_obj->{'value'}; + my $bool = $query_obj->{'bool'}; + my $contains = $query_obj->{'contains'}; + my $operator = ($contains eq 'nocontains') ? '!~*' : '~*'; + my $search_query; + if ($type eq 'instructor') { + my $in = ($contains eq 'nocontains') ? "not in" : "in"; + $search_query = {'id' => {$in => { + 'from' => 'acmcu', + 'select' => {'acmcu' => ['course']}, + 'where' => {'usr' => {'in' => { + 'from' => 'au', + 'select' => {'au' => ['id']}, + 'where' => { + '-or' => [ + {'pref_first_given_name' => {'~*' => $query}}, + {'first_given_name' => {'~*' => $query}}, + {'pref_second_given_name' => {'~*' => $query}}, + {'second_given_name' => {'~*' => $query}}, + {'pref_family_name' => {'~*' => $query}}, + {'family_name' => {'~*' => $query}} + ] + } + }}} + }}}; + } else { + $search_query = ($contains eq 'nocontains') ? + {'+acmc' => { $type => {$operator => $query}}} : + {$type => {$operator => $query}}; + } + + if ($bool eq 'or') { + push @$or_terms, $search_query; + } + + if ($bool eq 'and') { + push @$and_terms, $search_query; + } + } + + if ($or_terms and @$or_terms > 0) { + if ($and_terms and @$and_terms > 0) { + push @$or_terms, $and_terms; + } + $where_clause = {'-or' => $or_terms}; + } else { + $where_clause = {'-and' => $and_terms}; + } + + my $hits = $e->json_query({ + "from" => "acmc", + "select" => {"acmc" => ['id']}, + "where" => $where_clause + }); + + my $results = $e->json_query({ + "from" => "acmc", + "select" => {"acmc" => [ + 'id', + 'name', + 'course_number', + 'section_number', + 'is_archived', + 'owning_lib' + ]}, + "limit" => $limit, + "offset" => $offset, + "order_by" => {"acmc" => ['id']}, + "where" => $where_clause + }); + for my $result (@$results) { + push @{$ctx->{courses}}, { + id => $result->{id}, + course_number => $result->{course_number}, + section_number => $result->{section_number}, + owning_lib => $result->{owning_lib}, + name => $result->{name}, + is_archived => $result->{is_archived}, + instructors => [] + } + } + + #$ctx->{courses} = $@courses;#[{id=>10, name=>"test", course_number=>"LIT"}]; + $ctx->{hit_count} = @$hits || 0; + #$ctx->{hit_count} = 0; + return Apache2::Const::OK; +} + +sub _prepare_course_search { + my ($cgi, $ctx) = @_; + + my ($user_query, @queries) = _prepare_query($cgi); + my $modifiers; + $user_query //= ''; + + my $query = $user_query; + $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter}; + + foreach ($cgi->param('modifier')) { + $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/; + + } + # filters + foreach (grep /^fi:/, $cgi->param) { + /:(-?\w+)$/ or next; + my $term = join(",", $cgi->param($_)); + $query .= " $1($term)" if length $term; + } + + return () unless $query; + + return ($user_query, $query, @queries); +} + +sub _prepare_query { + my $cgi = shift; + + return $cgi->param('query') unless $cgi->param('qtype'); + + my %parts; + my @part_names = qw/qtype contains query bool modifier/; + $parts{$_} = [ $cgi->param($_) ] for (@part_names); + + my $full_query = ''; + my @queries; + for (my $i = 0; $i < scalar @{$parts{'qtype'}}; $i++) { + my ($qtype, $contains, $query, $bool, $modifier) = map { $parts{$_}->[$i] } @part_names; + next unless $query =~ /\S/; + + $contains = "" unless defined $contains; + + push @queries, { + contains => $contains, + bool => $bool, + qtype => $qtype, + value => $query + }; + + $bool = ($bool and $bool eq 'or') ? '||' : '&&'; + + $query = "$qtype:$query"; + + $full_query = $full_query ? "($full_query $bool $query)" : $query; + } + + return ($full_query, @queries); } \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/course/results.tt2 b/Open-ILS/src/templates/opac/course/results.tt2 new file mode 100644 index 0000000000..f8ae3b971a --- /dev/null +++ b/Open-ILS/src/templates/opac/course/results.tt2 @@ -0,0 +1,114 @@ +[%- PROCESS "opac/parts/header.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Course Search Results"); + page = CGI.param('page'); + page = page.match('^\d+$') ? page : 0; # verify page is a sane value + + page_count = (!ctx.page_size.defined || !ctx.hit_count.defined || ctx.page_size == 0) ? 1 : POSIX.ceil(ctx.hit_count / ctx.page_size); + + # We don't want search engines indexing search results + ctx.metalinks.push(''); + + PROCESS "opac/parts/misc_util.tt2"; + PROCESS get_library; + ctx.result_start = 1 + ctx.page_size * page; + ctx.result_stop = ctx.page_size * (page + 1); + IF ctx.result_stop > ctx.hit_count; ctx.result_stop = ctx.hit_count; END; + + result_count = ctx.result_start; +-%] +

[% l('Course Search Results') %]

+[% INCLUDE "opac/parts/searchbar.tt2" %] +
+ +
+
+
+
+ [% PROCESS "opac/parts/result/paginate.tt2" %] + [% ctx.results_count_header = PROCESS results_count_header; + ctx.results_count_header %] +
+
+ + + + + + + + + + [% FOR course IN ctx.courses %] + [% course_url_path = ctx.opac_root _ '/course/' _ course.id; %] + [% # Do not pass "advanced params" to result detail code. + # Instead, pass the scrubed query in one-line form + del_parms = del_parms.merge(['query', 'bool', + 'qtype', 'contains', '_adv']); + add_parms.import( + {query => ctx.naive_query_scrub(ctx.user_query)} ); + %] + + + + + + + [% END %] + +
[% l('Search result number') %][% l('Course details') %]
+ [% result_count; result_count = result_count + 1 %]. + + + + [% ctx.get_aou(course.owning_lib).name %] +
+
+
+
+
+
+
+
+[%- END %] diff --git a/Open-ILS/src/templates/opac/course_search.tt2 b/Open-ILS/src/templates/opac/course_search.tt2 new file mode 100644 index 0000000000..979930a393 --- /dev/null +++ b/Open-ILS/src/templates/opac/course_search.tt2 @@ -0,0 +1,95 @@ +[%- PROCESS "opac/parts/header.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Course Search"); + + PROCESS "opac/parts/misc_util.tt2"; + PROCESS get_library; + ctx.metalinks.push(''); +-%] + +

[% l('Course Search') %]

+ +
+ +
+ +
+
+
+ +
+ [% l("Course Search Input") %] +
+ +
+
+
+ +
+
+ [% PROCESS "opac/parts/org_selector.tt2" %] + [% INCLUDE build_org_selector show_loc_groups=1 id=org.id %] + + + + +
+
+
+ +
+ +
+
+
+
+
+ +[% END %] \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/css/style.css.tt2 b/Open-ILS/src/templates/opac/css/style.css.tt2 index cf1bfff523..985ee01a1a 100644 --- a/Open-ILS/src/templates/opac/css/style.css.tt2 +++ b/Open-ILS/src/templates/opac/css/style.css.tt2 @@ -1416,6 +1416,10 @@ div.result_table_utils_cont { font-size: [% css_fonts.size_bigger %]; } +.search_courses_label { + font-size: [% css_fonts.size_bigger %]; +} + .lbl1 { font-size: [% css_fonts.size_bigger %]; font-weight:bold; diff --git a/Open-ILS/src/templates/opac/parts/course_search/global_row.tt2 b/Open-ILS/src/templates/opac/parts/course_search/global_row.tt2 new file mode 100644 index 0000000000..d68842f09b --- /dev/null +++ b/Open-ILS/src/templates/opac/parts/course_search/global_row.tt2 @@ -0,0 +1,55 @@ +[% + contains_options = [ + {value => 'contains', label => l('Contains')}, + {value => 'nocontains', label => l('Does not contain')} + ]; + contains = CGI.param('contains'); + queries = CGI.param('query'); + bools = CGI.param('bool'); + qtypes = CGI.param('qtype'); + rowcount = 3; + + # scalar.merge treats the scalar as a 1-item array + WHILE queries.size < rowcount; queries = queries.merge(['']); END; + WHILE bools.size < rowcount; bools = bools.merge(['and']); END; + WHILE qtypes.size < rowcount; qtypes = qtypes.merge(search.default_qtypes.${qtypes.size} ? [search.default_qtypes.${qtypes.size}] : ['keyword']); END; + + FOR qtype IN qtypes; + c = contains.shift; + b = bools.shift; + q = queries.shift; %] + + + + + + + + + + + [% INCLUDE "opac/parts/course_search/qtype_selector.tt2" + query_type=qtype %] + + + + + + [% l('Remove row') %] + + + +[% END %] diff --git a/Open-ILS/src/templates/opac/parts/course_search/qtype_selector.tt2 b/Open-ILS/src/templates/opac/parts/course_search/qtype_selector.tt2 new file mode 100644 index 0000000000..a9770b1fe0 --- /dev/null +++ b/Open-ILS/src/templates/opac/parts/course_search/qtype_selector.tt2 @@ -0,0 +1,20 @@ +[% query_types = [ + {value => "name", label => l("Title"), plural_label => l("Titles"), browse => 1}, + {value => "instructor", label => l("Instructor"), plural_label => l('Instructors')}, + {value => "course_number", label => l("Course Number")} +]; +-%] + diff --git a/Open-ILS/src/templates/opac/parts/searchbar.tt2 b/Open-ILS/src/templates/opac/parts/searchbar.tt2 index d86bd7dc2e..72139869a9 100644 --- a/Open-ILS/src/templates/opac/parts/searchbar.tt2 +++ b/Open-ILS/src/templates/opac/parts/searchbar.tt2 @@ -44,6 +44,9 @@ END; [% l('Advanced Search') %] [% l('Browse the Catalog') %] + [% IF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %] + [% l('Search for Courses') %] + [% END %] [% INCLUDE 'opac/parts/cart.tt2' %]