File Coverage

lib/CGI/OptimalQuery/Base.pm
Criterion Covered Total %
statement 33 283 11.6
branch 0 126 0.0
condition 0 54 0.0
subroutine 11 46 23.9
pod 0 34 0.0
total 44 543 8.1


";
line stmt bran cond sub pod time code
1             package CGI::OptimalQuery::Base;
2              
3 8     8   1077 use strict;
  8         20  
  8         220  
4 8     8   34 use warnings;
  8         15  
  8         202  
5 8     8   31 no warnings qw( uninitialized redefine );
  8         16  
  8         224  
6              
7 8     8   836 use CGI();
  8         26766  
  8         199  
8 8     8   56 use Carp('confess');
  8         15  
  8         718  
9 8     8   3871 use POSIX();
  8         44575  
  8         194  
10 8     8   3742 use DBIx::OptimalQuery;
  8         33  
  8         267  
11 8     8   4874 use JSON::XS;
  8         20203  
  8         379  
12              
13             # some tools that OQ auto activates
14 8     8   3127 use CGI::OptimalQuery::ExportDataTool();
  8         19  
  8         142  
15 8     8   3520 use CGI::OptimalQuery::SaveSearchTool();
  8         23  
  8         185  
16 8     8   3373 use CGI::OptimalQuery::LoadSearchTool();
  8         28  
  8         25999  
17              
18             sub escapeHTML {
19 0     0 0   local ($_) = @_;
20 0           s{&}{&}gso;
21 0           s{<}{<}gso;
22 0           s{>}{>}gso;
23 0           s{"}{"}gso;
24 0           s{'}{'}gso;
25 0           s{\x8b}{‹}gso;
26 0           s{\x9b}{›}gso;
27 0           return $_;
28             }
29              
30 0     0 0   sub can_embed { 0 }
31              
32             # alias for output
33             sub print {
34 0     0 0   my $o = shift;
35 0           $o->output(@_);
36             }
37              
38             sub new {
39 0     0 0   my $pack = shift;
40 0           my $schema = shift;
41 0 0         die "could not find schema!" unless ref($schema) eq 'HASH';
42              
43 0           my $o = bless {}, $pack;
44              
45 0           $$o{schema} = clone($schema);
46              
47             $$o{dbh} = $$o{schema}{dbh}
48 0 0         or confess "couldn't find dbh in schema!";
49             $$o{q} = $$o{schema}{q}
50 0 0         or confess "couldn't find q in schema!";
51 0           $$o{output_handler} = $$o{schema}{output_handler};
52 0           $$o{error_handler} = $$o{schema}{error_handler};
53 0           $$o{httpHeader} = $$o{schema}{httpHeader};
54              
55             # check for required attributes
56             confess "specified select is not a hash ref!"
57 0 0         unless ref $$o{schema}{select} eq "HASH";
58             confess "specified joins is not a hash ref!"
59 0 0         unless ref $$o{schema}{joins} eq "HASH";
60            
61             # set defaults
62 0   0       $$o{schema}{debug} ||= 0;
63             $$o{schema}{check} = $ENV{'CGI-OPTIMALQUERY_CHECK'}
64 0 0         if ! defined $$o{schema}{check};
65 0 0         $$o{schema}{check} = 0 if ! defined $$o{schema}{check};
66 0   0       $$o{schema}{title} ||= "";
67 0   0       $$o{schema}{options} ||= {};
68 0   0       $$o{schema}{resourceURI} ||= $ENV{OPTIMALQUERY_RESOURCES} || '/OptimalQuery';
      0        
69              
70 0 0         if (! $$o{schema}{URI}) {
71 0 0         $_ = ($$o{q}->can('uri')) ? $$o{q}->uri() : $ENV{REQUEST_URI}; s/\?.*$//;
  0            
72 0           $$o{schema}{URI} = $_;
73             # disabled so we can run from command line for testing where REQUEST_URI probably isn't defined
74             # or die "could not find 'URI' in schema";
75             }
76              
77 0   0       $$o{schema}{URI_standalone} ||= $$o{schema}{URI};
78              
79             # make sure developer is not using illegal state_params
80 0 0         if (ref($$o{schema}{state_params}) eq 'ARRAY') {
81 0           foreach my $p (@{ $$o{schema}{state_params} }) {
  0            
82 0 0         die "cannot use reserved state param name: act" if $p eq 'act';
83 0 0         die "cannot use reserved state param name: module" if $p eq 'module';
84 0 0         die "cannot use reserved state param name: view" if $p eq 'view';
85             }
86             }
87              
88             # construct optimal query object
89             $$o{oq} = DBIx::OptimalQuery->new(
90             'dbh' => $$o{schema}{dbh},
91             'select' => $$o{schema}{select},
92             'joins' => $$o{schema}{joins},
93             'named_filters' => $$o{schema}{named_filters},
94             'named_sorts' => $$o{schema}{named_sorts},
95             'debug' => $$o{schema}{debug},
96             'error_handler' => $$o{schema}{error_handler}
97 0           );
98              
99             # the following code is responsible for setting the disable_sort flag for all
100             # multi valued selects (since it never makes since to sort a m-valued column)
101 0           my %cached_dep_multival_status;
102             my $find_dep_multival_status_i;
103 0           my $find_dep_multival_status;
104             $find_dep_multival_status = sub {
105 0     0     my $joinAlias = shift;
106 0           $find_dep_multival_status_i++;
107 0 0         die "could not resolve join alias: $joinAlias deps" if $find_dep_multival_status_i > 100;
108 0 0         if (! exists $cached_dep_multival_status{$joinAlias}) {
109 0           my $v;
110 0 0         if (exists $$o{oq}{joins}{$joinAlias}[3]{new_cursor}) { $v = 0; }
  0 0          
111 0           elsif (! @{ $$o{oq}{joins}{$joinAlias}[0] }) { $v = 1; }
  0            
112 0           else { $v = $find_dep_multival_status->($$o{oq}{joins}{$joinAlias}[0][0]); }
113 0           $cached_dep_multival_status{$joinAlias} = $v;
114             }
115 0           return $cached_dep_multival_status{$joinAlias};
116 0           };
117              
118             # loop though all selects
119 0           foreach my $selectAlias (keys %{ $$o{oq}{select} }) {
  0            
120 0           $find_dep_multival_status_i = 0;
121              
122             # set the disable sort flag is select is a multi value
123             $$o{oq}{select}{$selectAlias}[3]{disable_sort} = 1
124 0 0         if ! $find_dep_multival_status->($$o{oq}{select}{$selectAlias}[0][0]);
125              
126             # set is_hidden flag if select does not have a nice name assigned
127             $$o{oq}{select}{$selectAlias}[3]{is_hidden} = 1
128 0 0         if ! $$o{oq}{select}{$selectAlias}[2];
129              
130             # if no SQL (could be a recview) then disable sort, filter
131 0 0         if (! $$o{oq}{select}{$selectAlias}[1]) {
132 0           $$o{oq}{select}{$selectAlias}[3]{disable_sort} = 1;
133 0           $$o{oq}{select}{$selectAlias}[3]{disable_filter} = 1;
134             }
135              
136             # if a select column has additional select fields specified in options, make sure that the options array is an array
137 0 0 0       if ($$o{oq}{select}{$selectAlias}[3]{select} && ref($$o{oq}{select}{$selectAlias}[3]{select}) ne 'ARRAY') {
138 0           my @x = split /\ *\,\ */, $$o{oq}{select}{$selectAlias}[3]{select};
139 0           $$o{oq}{select}{$selectAlias}[3]{select} = \@x;
140             }
141             }
142              
143             # if any fields are passed into on_select, ensure they are always selected
144 0           my $on_select = $$o{q}->param('on_select');
145 0 0         if ($on_select =~ /[^\,]+\,(.+)/) {
146 0           my @fields = split /\,/, $1;
147 0           for (@fields) {
148             $$o{oq}{'select'}{$_}[3]{always_select}=1
149 0 0         if exists $$o{oq}{'select'}{$_};
150             }
151             }
152              
153             # check schema validity
154 0 0 0       $$o{oq}->check_join_counts() if $$o{schema}{check} && ! defined $$o{q}->param('module');
155              
156             # install the export tool
157 0           CGI::OptimalQuery::ExportDataTool::activate($o);
158              
159             # if savedSearchUserID enable savereport and loadreport tools
160 0   0       $$o{schema}{savedSearchUserID} ||= undef;
161 0 0         if ($$o{schema}{savedSearchUserID} =~ /^\d+$/) {
162 0           CGI::OptimalQuery::LoadSearchTool::activate($o);
163 0           CGI::OptimalQuery::SaveSearchTool::activate($o);
164             }
165              
166             # run on_init function for each enabled tool
167 0           foreach my $v (values %{ $$o{schema}{tools} }) {
  0            
168 0 0         $$v{on_init}->($o) if ref($$v{on_init}) eq 'CODE';
169             }
170              
171 0   0       my $schemaparams = $$o{schema}{params} || {};
172 0           foreach my $k (qw( page rows_page show filter hiddenFilter queryDescr sort mode )) {
173 0 0         if (exists $$schemaparams{$k}) {
    0          
174 0           $$o{$k} = $$schemaparams{$k};
175             } elsif (defined $$o{q}->param($k)) {
176 0           $$o{$k} = $$o{q}->param($k);
177             } else {
178 0           $$o{$k} = $$o{schema}{$k};
179             }
180             }
181              
182 0   0       $$o{mode} ||= 'default';
183 0           $$o{mode} =~ s/\W//g;
184              
185 0   0       $$o{schema}{results_per_page_picker_nums} ||= [25,50,100,500,1000,'All'];
186 0   0       $$o{rows_page} ||= $$o{schema}{rows_page} || $$o{schema}{results_per_page_picker_nums}[0] || 10;
      0        
187 0   0       $$o{page} ||= 1;
188              
189             # convert show into array
190 0 0         if (! ref($$o{show})) {
191 0           my @ar = split /\,/, $$o{show};
192 0           $$o{show} = \@ar;
193             }
194              
195             # if we still don't have something to show then show all cols
196             # that aren't hidden
197 0 0         if (! scalar( @{ $$o{show} } )) {
  0            
198 0           for (keys %{ $$o{schema}{select} }) {
  0            
199 0 0         push @{$$o{show}}, $_ unless $$o{oq}->{'select'}->{$_}->[3]->{is_hidden};
  0            
200             }
201             }
202              
203 0           return $o;
204             }
205              
206 0     0 0   sub oq { $_[0]{oq} }
207              
208             # ----------- UTILITY METHODS ------------------------------------------------
209              
210 0     0 0   sub escape_html { escapeHTML($_[1]) }
211 0     0 0   sub escape_uri { CGI::escape($_[1]) }
212             sub escape_js {
213 0     0 0   my $o = shift;
214 0           $_ = shift;
215 0           s/\\/\\x5C/g; #escape \
216 0           s/\n/\\x0A/g; #escape new lines
217 0           s/\'/\\x27/g; #escape '
218 0           s/\"/\\x22/g; #escape "
219 0           s/\&/\\x26/g; #escape &
220 0           s/\r//g; #remove carriage returns
221 0           s/script/scr\\x69pt/ig; # make nice script tags
222 0           return $_;
223             }
224             sub commify {
225 0     0 0   my $o = shift;
226 0           my $text = reverse $_[0];
227 0           $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
228 0           return scalar reverse $text;
229             } # Commify
230              
231              
232             my %no_clone = ('dbh' => 1, 'q' => 1);
233             sub clone {
234 0     0 0   my $thing = shift;
235 0 0         if (ref($thing) eq 'HASH') {
    0          
236 0           my %tmp;
237 0           while (my ($k,$v) = each %$thing) {
238 0 0         if (exists $no_clone{$k}) { $tmp{$k} = $v; }
  0            
239 0           else { $tmp{$k} = clone($v); }
240             }
241 0           $thing = \%tmp;
242             } elsif (ref($thing) eq 'ARRAY') {
243 0           my @tmp;
244 0           foreach my $v (@$thing) { push @tmp, clone($v); }
  0            
245 0           $thing = \@tmp;
246             }
247 0           return $thing;
248             }
249              
250              
251              
252             #-------------- ACCESSORS --------------------------------------------------
253             sub sth {
254 0     0 0   my ($o) = @_;
255 0 0         return $$o{sth} if $$o{sth};
256              
257             # show is made up of all the fields that should be selected
258 0           my @show; {
259 0           my %show;
  0            
260 0           foreach my $colalias (@{$$o{show}}) {
  0            
261 0 0         if (ref($$o{schema}{select}{$colalias}[3]{select}) eq 'ARRAY') {
262 0           $show{$_}=1 for @{ $$o{schema}{select}{$colalias}[3]{select} };
  0            
263             }
264 0 0         if ($$o{schema}{select}{$colalias}[1]) {
265 0           $show{$colalias}=1;
266             }
267             }
268 0           @show = sort keys %show;
269             }
270              
271             # create & execute SQL statement
272             $$o{sth} = $$o{oq}->prepare(
273             show => \@show,
274             filter => $$o{filter},
275             hiddenFilter => $$o{hiddenFilter},
276             forceFilter => $$o{schema}{forceFilter},
277 0           sort => $$o{sort} );
278              
279             # current fetched row
280 0           $$o{rec} = undef;
281              
282             # calculate what the limit is
283             # and make sure page, num_pages, rows_page make sense
284 0 0 0       if ($$o{sth}->count() == 0) {
    0          
285 0           $$o{page} = 0;
286 0           $$o{rows_page} = 0;
287 0           $$o{num_pages} = 0;
288 0           $$o{limit} = [0,0];
289             } elsif ($$o{rows_page} eq 'All' || ($$o{sth}->count() < $$o{rows_page})) {
290 0           $$o{rows_page} = "All";
291 0           $$o{page} = 1;
292 0           $$o{num_pages} = 1;
293 0           $$o{limit} = [1, $$o{sth}->count()];
294             } else {
295 0           $$o{num_pages} = POSIX::ceil($$o{sth}->count() / $$o{rows_page});
296 0 0         $$o{page} = $$o{num_pages} if $$o{page} > $$o{num_pages};
297 0           my $lo = ($$o{rows_page} * $$o{page}) - $$o{rows_page} + 1;
298 0           my $hi = $lo + $$o{rows_page} - 1;
299 0 0         $hi = $$o{sth}->count() if $hi > $$o{sth}->count();
300 0           $$o{limit} = [$lo, $hi];
301             }
302              
303 0           $$o{sth}->set_limit($$o{limit});
304              
305 0           return $$o{sth};
306             }
307 0     0 0   sub get_count { $_[0]->sth->count() }
308 0     0 0   sub get_rows_page { $_[0]{rows_page} }
309 0     0 0   sub get_current_page { $_[0]{page} }
310 0     0 0   sub get_lo_rec { $_[0]->sth->get_lo_rec() }
311 0     0 0   sub get_hi_rec { $_[0]->sth->get_hi_rec() }
312 0     0 0   sub get_num_pages { $_[0]{num_pages} }
313 0     0 0   sub get_title { $_[0]{schema}{title} }
314 0     0 0   sub get_filter { $_[0]->sth->filter_descr() }
315 0     0 0   sub get_sort { $_[0]->sth->sort_descr() }
316 0     0 0   sub get_query { $_[0]{query} }
317             sub get_nice_name {
318 0     0 0   my ($o, $colAlias) = @_;
319             return $$o{schema}{select}{$colAlias}[2]
320 0   0       || join(' ', map { ucfirst } split /[\ \_]+/, $colAlias);
321             }
322 0     0 0   sub get_num_usersel_cols { scalar @{$_[0]{show}} }
  0            
323 0     0 0   sub get_usersel_cols { $_[0]{show} }
324              
325             sub finish {
326 0     0 0   my ($o) = @_;
327 0 0         $$o{sth}->finish() if $$o{sth};
328 0           undef $$o{sth};
329             }
330              
331             # get the options
332             sub get_opts {
333 0     0 0   my ($o) = @_;
334              
335 0 0         if (! $$o{_opts}) {
336 0           my $class = ref $o;
337              
338 0 0 0       if (exists $$o{schema}{options}{$class}) {
    0          
339 0           $$o{_opts} = $$o{schema}{options}{$class};
340             }
341              
342             # remove numerics and try again, this allows for module developers to create upgraded modules that use
343             # backwards compatible options example: InteractiveQuery & InteractiveQuery2
344             elsif ($class =~ s/\d+$// && exists $$o{schema}{options}{$class}) {
345 0           $$o{_opts} = $$o{schema}{options}{$class};
346             }
347              
348             else {
349 0           $$o{_opts} = {};
350             }
351             }
352              
353 0           return $$o{_opts};
354             }
355              
356             sub fetch {
357 0     0 0   my ($o) = @_;
358 0 0         if ($$o{rec} = $o->sth->fetchrow_hashref()) {
359 0           my $mutator = $o->get_opts()->{'mutateRecord'};
360 0 0         $mutator->($$o{rec}) if ref($mutator) eq 'CODE';
361 0 0         $$o{schema}{mutateRecord}->($$o{rec}) if ref($$o{schema}{mutateRecord}) eq 'CODE';
362 0           return $$o{rec};
363             }
364 0           return undef;
365             }
366              
367             sub get_val {
368 0     0 0   my ($o, $colAlias) = @_;
369 0 0         $o->fetch() unless $$o{rec};
370 0   0       my $formatter = $$o{schema}{select}{$colAlias}[3]{formatter} || \&default_formatter;
371 0           return $formatter->($$o{rec}{$colAlias}, $$o{rec}, $o, $colAlias);
372             }
373              
374             sub get_html_val {
375 0     0 0   my ($o, $colAlias) = @_;
376 0 0         $o->fetch() unless $$o{rec};
377 0   0       my $formatter = $$o{schema}{select}{$colAlias}[3]{html_formatter} || \&default_html_formatter;
378 0           return $formatter->($$o{rec}{$colAlias}, $$o{rec}, $o, $colAlias);
379             }
380              
381             sub default_formatter {
382 0     0 0   my ($val) = @_;
383 0 0         return (ref($val) eq 'ARRAY') ? join(', ', @$val) : $val;
384             }
385              
386             sub default_html_formatter {
387 0     0 0   my ($val, $rec, $o, $colAlias) = @_;
388 0 0         if (! exists $$o{_noEscapeColMap}) {
389 0 0         my %noEsc = map { $_ => 1 } @{ $o->get_opts()->{'noEscapeCol'} || [] };
  0            
  0            
390 0           $$o{_noEscapeColMap} = \%noEsc;
391             }
392 0 0         if ($$o{_noEscapeColMap}{$colAlias}) {
    0          
393 0 0         $val = join(' ', @$val) if ref($val) eq 'ARRAY';
394             } elsif (ref($val) eq 'ARRAY') {
395 0           $val = join(', ', map { escapeHTML($_) } @$val);
  0            
396             } else {
397 0           $val = escapeHTML($val);
398             }
399 0           return $val;
400             }
401              
402             sub recview_formatter {
403 0     0 0   my ($val, $rec, $o, $colAlias) = @_;
404              
405 0           my @val;
406 0           foreach my $colAlias2 (@{ $$o{schema}{select}{$colAlias}[3]{select} }) {
  0            
407 0           my $val2 = default_formatter($$rec{$colAlias2});
408 0 0         if ($val2 ne '') {
409 0   0       my $label = $$o{schema}{select}{$colAlias2}[2] || $colAlias2;
410 0           push @val, "$label: $val2";
411             }
412             }
413 0           return join("\n", @val);
414             }
415              
416             sub recview_html_formatter {
417 0     0 0   my ($val, $rec, $o, $colAlias) = @_;
418              
419 0           my @val;
420 0           foreach my $colAlias2 (@{ $$o{schema}{select}{$colAlias}[3]{select} }) {
  0            
421 0           my $val2 = $o->get_html_val($colAlias2);
422 0 0         if ($val2 ne '') {
423 0   0       my $label = $$o{schema}{select}{$colAlias2}[2] || $colAlias2;
424 0           push @val, "
".escapeHTML($label)."$val2
425             }
426             }
427 0 0         return $#val > -1 ? "".join('', @val)."
" : '';
428             }
429              
430             sub get_link {
431 0     0 0   my ($o) = @_;
432 0           my @args;
433 0           foreach my $k (qw( show filter hiddenFilter queryDescr sort)) {
434 0           my $v1 = $$o{$k};
435 0 0         $v1 = join(',', @$v1) if ref($v1) eq 'ARRAY';
436 0           my $v2 = $$o{schema}{$k};
437 0 0         $v2 = join(',', @$v2) if ref($v2) eq 'ARRAY';
438 0 0         push @args, "$k=".CGI::escape($v1) if $v1 ne $v2;
439             }
440 0           my $rv = $$o{schema}{URI};
441 0           my $args = join('&', @args);
442 0 0         $rv .= '?'.$args if $args;
443 0           return $rv;
444             }
445              
446             1;