]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm
LP 2061136 follow-up: ng lint --fix
[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.courses',
26         'open-ils.courses.courses.retrieve',
27         [$course_id]
28     )->[0];
29     
30     $ctx->{instructors} = $U->simplereq(
31         'open-ils.courses',
32         'open-ils.courses.course_users.retrieve',
33         $course_id
34     );
35
36     $ctx->{course_materials} = $U->simplereq(
37         'open-ils.courses',
38         'open-ils.courses.course_materials.retrieve.fleshed.atomic',
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         my $locg = $cgi->param('locg');
57         my $pivot = scalar($self->cgi->param('bpivot'));
58         my $limit = int(
59             $self->cgi->param('blimit') ||
60             $self->ctx->{opac_hits_per_page} || 10
61         );
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.
65         my $results;
66         my $instructors;
67         if ($qtype eq 'instructor') {
68             $instructors = $e->json_query({
69                 "from" => "acmcu",
70                 "select" => {"acmcu" => [
71                     'id',
72                     'usr',
73                 ]},
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'}}}}
76             });
77             $results = $e->json_query({
78                 "from" => "au",
79                 "select" => {"au" => [
80                     'id',
81                     'pref_first_given_name',
82                     'first_given_name',
83                     'pref_second_given_name',
84                     'second_given_name',
85                     'pref_family_name',
86                     'family_name'
87                 ]},
88                 "order_by" => {'au' => ['pref_family_name', 'family_name']},
89                 "where" => {'-and' => [{
90                     "id" => { "in" => {
91                         "from" => "acmcu",
92                         "select" => {
93                             "acmcu" => ['usr']
94                         },
95                         "where" => {'-and' => [
96                             {'usr_role' => { 'in' => {
97                                 'from' => 'acmr',
98                                 "select" => {
99                                     "acmr" => ['id']
100                                 },
101                                 "where" => {'+acmr' => 'is_public'}}}},
102                             {"course" => { "in" =>{
103                                 "from" => "acmc",
104                                 "select" => {
105                                     "acmc" => ['id']
106                                 },
107                                 "where" => {'-not' => [{'+acmc' => 'is_archived'}]}
108                             }}}
109                         ]}
110                     }}
111                 }]}
112             });
113         } else {
114             $results = $e->json_query({
115                 "from" => "acmc",
116                 "select" => {"acmc" => [
117                     'id',
118                     'name',
119                     'course_number',
120                     'is_archived',
121                     'owning_lib'
122                 ]},
123                 "order_by" => {"acmc" => [$qtype]},
124                 "where" => {
125                     '-not' => {'+acmc' => 'is_archived'},
126                     '+acmc' => { 'owning_lib' => $U->get_org_descendants($locg) }
127                 }
128             });
129         }
130
131         my $bterm_match = 0;
132         for my $result(@$results) {
133             my $value_exists = 0;
134             my $rqtype = $qtype;
135             my $entry = {
136                 'value' => '',
137                 'results_count' => 0,
138                 'match' => 0
139             };
140
141             if ($qtype eq 'instructor') {
142                 # Put together the name
143                 my $name_str = '';
144                 if ($result->{'pref_family_name'}) {
145                     $name_str = $result->{'pref_family_name'} . ", ";
146                 } elsif ($result->{'family_name'}) {
147                     $name_str = $result->{'family_name'} . ", ";
148                 }
149
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'};
154                 }
155
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'};
160                 }
161
162                 $result->{$rqtype} = $name_str;
163
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;
168                         last;
169                     }
170                 }
171             } else {
172                 $entry->{'results_count'} += 1;
173             }
174
175             for my $existing_entry(@$browse_results) {
176                 if ($existing_entry->{'value'} eq $result->{$rqtype} && $value_exists eq 0) {
177                     $value_exists = 1;
178                     $existing_entry->{'results_count'} += 1;
179                     last;
180                 }
181             }
182
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) {
187                         $bterm_match = 1;
188                         $entry->{'match'} = 1;
189                     }
190                 }
191                 $entry->{'value'} = $result->{$rqtype};
192                 push @$browse_results, $entry;
193             }
194         }
195         # Feels a bit hacky, but we need the index of the matching entry
196         my $match_idx = 0;
197         if ($bterm_match) {
198             for my $i (0..$#$browse_results) {
199                 if ($browse_results->[$i]->{'match'}) {
200                     $match_idx = $i;
201                     last;
202                 }
203             }
204         }
205
206         for my $i(0..$#$browse_results) {
207             $browse_results->[$i]->{'browse_index'} = $i;
208         }
209         $ctx->{match_idx} = $match_idx;
210         $ctx->{browse_results} = $browse_results;
211     }
212
213     return Apache2::Const::OK;
214 }
215
216 sub load_cresults {
217     my $self = shift;
218     my %args = @_;
219     my $internal = $args{internal};
220     my $cgi = $self->cgi;
221     my $ctx = $self->ctx;
222     my $e = $self->editor;
223     my $limit = 10;
224
225     $ctx->{page} = 'cresult' unless $internal;
226     $ctx->{ids} = [];
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;
232     my $results;
233     $ctx->{page_size} = $limit;
234     $ctx->{search_page} = $page;
235     $ctx->{pagable_limit} = 50;
236
237     # fetch this page plus the first hit from the next page
238     if ($internal) {
239         $limit = $offset + $limit + 1;
240         $offset = 0;
241     }
242
243     my ($user_query, $query, @queries, $modifiers) = _prepare_course_search($cgi, $ctx);
244
245     #return Apache2::Const::OK unless $query;
246
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});
253
254     my $where_clause = _create_where_clause($ctx, \@queries, $search_orgs);
255
256     my $hits = $e->json_query({
257         "from" => "acmc",
258         "select" => {"acmc" => ['id']},
259         "where" => $where_clause
260     });
261
262     
263     $results = $e->json_query({
264         "from" => "acmc",
265         "select" => {"acmc" => [
266             'id',
267             'name',
268             'course_number',
269             'section_number',
270             'is_archived',
271             'owning_lib'
272         ]},
273         "limit" => $limit,
274         "offset" => $offset,
275         "order_by" => {"acmc" => ['id']},
276         "where" => $where_clause
277     });
278     for my $result (@$results) {
279         push @{$ctx->{courses}}, {
280             id => $result->{id},
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},
286             instructors => []
287         }
288     }
289
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;
294 }
295
296 sub _create_where_clause {
297     my ($ctx, $queries, $search_orgs) = @_;
298
299     my $where_clause;
300     my $and_terms = [];
301     my $or_terms = [];
302
303     push @$and_terms, {'owning_lib' => $search_orgs};
304     push @$and_terms, {'-not' => {'+acmc' => 'is_archived'}} unless $ctx->{processed_search_query} =~ qr\#include_archived\;
305
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'};
310
311         # Remove punctuation except for the the * wildcard
312         $query =~ s/\w\s\*//;
313
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') ? '!~*' : '~*';
319         my $search_query;
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']},
328                 'where' => [
329                     {"+acmr"=>"is_public"},
330                     {"usr"=>{"in"=>
331                         {"select"=>{"au"=>["id"]},
332                         "where"=>{"name_kw_tsvector"=>
333                         {"@@"=>{"value"=>["to_tsquery",$query]}}},
334                     "from"=>"au"}}}]
335             }}};
336         } else {
337             $query =~ s/\*/.*/;
338             $search_query = ($contains eq 'nocontains') ?
339               {'+acmc' => { $type => {$operator => $query}}} :
340               {$type => {$operator => $query}};
341         }
342
343         if ($bool eq 'or') {
344             push @$or_terms, $search_query;
345         }
346
347         if ($bool eq 'and') {
348             push @$and_terms, $search_query;
349         }
350     }
351
352     if ($or_terms and @$or_terms > 0) {
353         if ($and_terms and @$and_terms > 0) {
354             push @$or_terms, $and_terms;
355         }
356         $where_clause = {'-or' => $or_terms};
357     } else {
358         $where_clause = {'-and' => $and_terms};
359     }
360     return $where_clause;
361 }
362
363 sub _prepare_course_search {
364     my ($cgi, $ctx) = @_;
365
366     my ($user_query, @queries) = _prepare_query($cgi);
367     my $modifiers;
368     $user_query //= '';
369
370     my $query = $user_query;
371     $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
372
373     foreach ($cgi->param('modifier')) {
374         $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
375
376     }
377     # filters
378     foreach (grep /^fi:/, $cgi->param) {
379         /:(-?\w+)$/ or next;
380         my $term = join(",", $cgi->param($_));
381         $query .= " $1($term)" if length $term;
382     }
383
384     #return () unless $query;
385
386     return ($user_query, $query, @queries);
387 }
388
389 sub _prepare_query {
390     my $cgi = shift;
391
392     return $cgi->param('query') unless $cgi->param('qtype');
393
394     my %parts;
395     my @part_names = qw/qtype contains query bool modifier/;
396     $parts{$_} = [ $cgi->param($_) ] for (@part_names);
397
398     my $full_query = '';
399     my @queries;
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/;
403
404         $contains = "" unless defined $contains;
405
406         push @queries, {
407             contains => $contains,
408             bool => $bool,
409             qtype => $qtype,
410             value => $query
411         };
412
413         $bool = ($bool and $bool eq 'or') ? '||' : '&&';
414
415         $query = "$qtype:$query";
416
417         $full_query = $full_query ? "($full_query $bool $query)" : $query;
418     }
419
420     return ($full_query, @queries);
421 }