File Coverage

lib/CGI/OptimalQuery/Base.pm
Criterion Covered Total %
statement 33 281 11.7
branch 0 126 0.0
condition 0 51 0.0
subroutine 11 46 23.9
pod 0 34 0.0
total 44 538 8.1


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