]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm
lp1849212 Course Browse
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / EGCatLoader / Course.pm
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;
8 use Net::HTTP::NB;
9 use IO::Select;
10 my $U = 'OpenILS::Application::AppUtils';
11
12 sub load_course {
13     my $self = shift;
14     my $ctx = $self->ctx;
15
16     $ctx->{page} = 'course';
17     $ctx->{readonly} = $self->cgi->param('readonly');
18
19     my $course_id = $ctx->{page_args}->[0];
20
21     return Apache2::Const::HTTP_BAD_REQUEST
22         unless $course_id and $course_id =~ /^\d+$/;
23
24     $ctx->{course} = $U->simplereq(
25         'open-ils.circ',
26         'open-ils.circ.courses.retrieve',
27         [$course_id]
28     )->[0];
29     
30     $ctx->{instructors} = $U->simplereq(
31         'open-ils.circ',
32         'open-ils.circ.course_users.retrieve',
33         $course_id
34     );
35
36     $ctx->{course_materials} = $U->simplereq(
37         'open-ils.circ',
38         'open-ils.circ.course_materials.retrieve.fleshed',
39         {course => $course_id}
40     );
41     return Apache2::Const::OK;
42 }
43
44 sub load_course_browse {
45     my $self = shift;
46     my $cgi = $self->cgi;
47     my $ctx = $self->ctx;
48     my $e = $self->editor;
49
50     my $browse_results = [];
51
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         # Search term is optional. If it's empty, start at the
57         # beginning. Otherwise, center results on a match.
58         # Regardless, we're listing everything, so retrieve all.
59         my $results;
60         my $instructors;
61         if ($qtype eq 'instructor') {
62             $instructors = $e->json_query({
63                 "from" => "acmcu",
64                 "select" => {"acmcu" => [
65                     'id',
66                     'usr',
67                     'is_public'
68                 ]},
69                 # TODO: We need to support the chosen library as well...
70                 "where" => {'+acmcu' => 'is_public'}
71             });
72             $results = $e->json_query({
73                 "from" => "au",
74                 "select" => {"au" => [
75                     'id',
76                     'pref_first_given_name',
77                     'first_given_name',
78                     'pref_second_given_name',
79                     'second_given_name',
80                     'pref_family_name',
81                     'family_name'
82                 ]},
83                 "order_by" => {'au' => ['pref_family_name', 'family_name']},
84                 "where" => {'-and' => [{
85                     "id" => { "in" => {
86                         "from" => "acmcu",
87                         "select" => {
88                             "acmcu" => ['usr']
89                         },
90                         "where" => {'-and' => [
91                             {'+acmcu' => 'is_public'},
92                             {"course" => { "in" =>{
93                                 "from" => "acmc",
94                                 "select" => {
95                                     "acmc" => ['id']
96                                 },
97                                 "where" => {'-not' => [{'+acmc' => 'is_archived'}]}
98                             }}}
99                         ]}
100                     }}
101                 }]}
102             });
103         } else {
104             $results = $e->json_query({
105                 "from" => "acmc",
106                 "select" => {"acmc" => [
107                     'id',
108                     'name',
109                     'course_number',
110                     'is_archived',
111                     'owning_lib'
112                 ]},
113                 "order_by" => {"acmc" => [$qtype]},
114                 # TODO: We need to support the chosen library as well...
115                 "where" => {'-not' => {'+acmc' => 'is_archived'}}
116             });
117         }
118         my $bterm_match = 0;
119         for my $result(@$results) {
120             my $value_exists = 0;
121             my $rqtype = $qtype;
122             my $entry = {
123                 'value' => '',
124                 'results_count' => 0,
125                 'match' => 0
126             };
127
128             if ($qtype eq 'instructor') {
129                 # Put together the name
130                 my $name_str = '';
131                 if ($result->{'pref_family_name'}) {
132                     $name_str = $result->{'pref_family_name'} . ", ";
133                 } elsif ($result->{'family_name'}) {
134                     $name_str = $result->{'family_name'} . ", ";
135                 }
136
137                 if ($result->{'pref_first_given_name'}) {
138                     $name_str .= $result->{'pref_first_given_name'};
139                 } elsif ($result->{'first_given_name'}) {
140                     $name_str .= $result->{'first_given_name'};
141                 }
142
143                 if ($result->{'pref_second_given_name'}) {
144                     $name_str .= " " . $result->{'pref_second_given_name'};
145                 } elsif ($result->{'second_given_name'}) {
146                     $name_str .= " " . $result->{'second_given_name'};
147                 }
148
149                 $result->{$rqtype} = $name_str;
150
151                 # Get an accurate count of matching courses
152                 for my $instructor(@$instructors) {
153                     if ($instructor->{'usr'} eq $result->{'id'}) {
154                         $entry->{'results_count'} += 1;
155                         last;
156                     }
157                 }
158             } else {
159                 $entry->{'results_count'} += 1;
160             }
161
162             for my $existing_entry(@$browse_results) {
163                 if ($existing_entry->{'value'} eq $result->{$rqtype} && $value_exists eq 0) {
164                     $value_exists = 1;
165                     $existing_entry->{'results_count'} += 1;
166                     last;
167                 }
168             }
169
170             if ($value_exists eq 0) {
171                 # For Name/Course Number browse queries...
172                 if ($bterm_match eq 0) {
173                     if ($result->{$qtype} =~ m/^$bterm./ || $result->{$qtype} eq $bterm) {
174                         $bterm_match = 1;
175                         $entry->{'match'} = 1;
176                     }
177                 }
178                 $entry->{'value'} = $result->{$rqtype};
179                 push @$browse_results, $entry;
180             }
181         }
182         # Feels a bit hacky, but we need the index of the matching entry
183         my $match_idx = 0;
184         if ($bterm_match) {
185             for my $i (0..$#$browse_results) {
186                 if ($browse_results->[$i]->{'match'}) {
187                     $match_idx = $i;
188                     last;
189                 }
190             }
191         }
192
193         for my $i(0..$#$browse_results) {
194             $browse_results->[$i]->{'browse_index'} = $i;
195         }
196         $ctx->{match_idx} = $match_idx;
197         $ctx->{browse_results} = $browse_results;
198     }
199
200     return Apache2::Const::OK;
201 }
202
203 sub load_cresults {
204     my $self = shift;
205     my %args = @_;
206     my $internal = $args{internal};
207     my $cgi = $self->cgi;
208     my $ctx = $self->ctx;
209     my $e = $self->editor;
210     my $limit = 10;
211
212     $ctx->{page} = 'cresult' unless $internal;
213     $ctx->{ids} = [];
214     $ctx->{courses} = [];
215     $ctx->{hit_count} = 0;
216     $ctx->{search_ou} = $self->_get_search_lib();
217     my $page = $cgi->param('page') || 0;
218     my $offset = $page * $limit;
219     my $results;
220     $ctx->{page_size} = $limit;
221     $ctx->{search_page} = $page;
222     $ctx->{pagable_limit} = 50;
223
224     # fetch this page plus the first hit from the next page
225     if ($internal) {
226         $limit = $offset + $limit + 1;
227         $offset = 0;
228     }
229
230     my ($user_query, $query, @queries, $modifiers) = _prepare_course_search($cgi, $ctx);
231
232     return Apache2::Const::OK unless $query;
233
234     $ctx->{user_query} = $user_query;
235     $ctx->{processed_search_query} = $query;
236     my $search_args = {};
237     my $course_numbers = ();
238     
239     my $where_clause;
240     my $and_terms = [];
241     my $or_terms = [];
242
243     # Handle is_archived checkbox and Org Selector
244     my $search_orgs = $U->get_org_descendants($ctx->{search_ou});
245     push @$and_terms, {'owning_lib' => $search_orgs};
246     push @$and_terms, {'-not' => {'+acmc' => 'is_archived'}} unless $query =~ qr\#include_archived\;
247
248     # Now let's push the actual queries
249     for my $query_obj (@queries) {
250         my $type = $query_obj->{'qtype'};
251         my $query = $query_obj->{'value'};
252         my $bool = $query_obj->{'bool'};
253         my $contains = $query_obj->{'contains'};
254         my $operator = ($contains eq 'nocontains') ? '!~*' : '~*';
255         my $search_query;
256         if ($type eq 'instructor') {
257             my $in = ($contains eq 'nocontains') ? "not in" : "in";
258             $search_query = {'id' => {$in => {
259                 'from' => 'acmcu',
260                 'select' => {'acmcu' => ['course']},
261                 'where' => {'usr' => {'in' => {
262                     'from' => 'au',
263                     'select' => {'au' => ['id']},
264                     'where' => {
265                         '-or' => [
266                             {'pref_first_given_name' => {'~*' => $query}},
267                             {'first_given_name' => {'~*' => $query}},
268                             {'pref_second_given_name' => {'~*' => $query}},
269                             {'second_given_name' => {'~*' => $query}},
270                             {'pref_family_name' => {'~*' => $query}},
271                             {'family_name' => {'~*' => $query}}
272                         ]
273                     }
274                 }}}
275             }}};
276         } else {
277             $search_query = ($contains eq 'nocontains') ?
278               {'+acmc' => { $type => {$operator => $query}}} :
279               {$type => {$operator => $query}};
280         }
281
282         if ($bool eq 'or') {
283             push @$or_terms, $search_query;
284         }
285
286         if ($bool eq 'and') {
287             push @$and_terms, $search_query;
288         }
289     }
290
291     if ($or_terms and @$or_terms > 0) {
292         if ($and_terms and @$and_terms > 0) {
293             push @$or_terms, $and_terms;
294         }
295         $where_clause = {'-or' => $or_terms};
296     } else {
297         $where_clause = {'-and' => $and_terms};
298     }
299
300     my $hits = $e->json_query({
301         "from" => "acmc",
302         "select" => {"acmc" => ['id']},
303         "where" => $where_clause
304     });
305
306     my $results = $e->json_query({
307         "from" => "acmc",
308         "select" => {"acmc" => [
309             'id',
310             'name',
311             'course_number',
312             'section_number',
313             'is_archived',
314             'owning_lib'
315         ]},
316         "limit" => $limit,
317         "offset" => $offset,
318         "order_by" => {"acmc" => ['id']},
319         "where" => $where_clause
320     });
321     for my $result (@$results) {
322         push @{$ctx->{courses}}, {
323             id => $result->{id},
324             course_number => $result->{course_number},
325             section_number => $result->{section_number},
326             owning_lib => $result->{owning_lib},
327             name => $result->{name},
328             is_archived => $result->{is_archived},
329             instructors => []
330         }
331     }
332
333     #$ctx->{courses} = $@courses;#[{id=>10, name=>"test", course_number=>"LIT"}];
334     $ctx->{hit_count} = @$hits || 0;
335     #$ctx->{hit_count} = 0;
336     return Apache2::Const::OK;
337 }
338
339 sub _prepare_course_search {
340     my ($cgi, $ctx) = @_;
341
342     my ($user_query, @queries) = _prepare_query($cgi);
343     my $modifiers;
344     $user_query //= '';
345
346     my $query = $user_query;
347     $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
348
349     foreach ($cgi->param('modifier')) {
350         $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
351
352     }
353     # filters
354     foreach (grep /^fi:/, $cgi->param) {
355         /:(-?\w+)$/ or next;
356         my $term = join(",", $cgi->param($_));
357         $query .= " $1($term)" if length $term;
358     }
359
360     return () unless $query;
361
362     return ($user_query, $query, @queries);
363 }
364
365 sub _prepare_query {
366     my $cgi = shift;
367
368     return $cgi->param('query') unless $cgi->param('qtype');
369
370     my %parts;
371     my @part_names = qw/qtype contains query bool modifier/;
372     $parts{$_} = [ $cgi->param($_) ] for (@part_names);
373
374     my $full_query = '';
375     my @queries;
376     for (my $i = 0; $i < scalar @{$parts{'qtype'}}; $i++) {
377         my ($qtype, $contains, $query, $bool, $modifier) = map { $parts{$_}->[$i] } @part_names;
378         next unless $query =~ /\S/;
379
380         $contains = "" unless defined $contains;
381
382         push @queries, {
383             contains => $contains,
384             bool => $bool,
385             qtype => $qtype,
386             value => $query
387         };
388
389         $bool = ($bool and $bool eq 'or') ? '||' : '&&';
390
391         $query = "$qtype:$query";
392
393         $full_query = $full_query ? "($full_query $bool $query)" : $query;
394     }
395
396     return ($full_query, @queries);
397 }