File Coverage

blib/lib/DataTables.pm
Criterion Covered Total %
statement 27 233 11.5
branch 0 84 0.0
condition 0 18 0.0
subroutine 9 26 34.6
pod 15 15 100.0
total 51 376 13.5


line stmt bran cond sub pod time code
1             package DataTables;
2              
3 1     1   24842 use 5.008008;
  1         4  
  1         41  
4 1     1   6 use strict;
  1         2  
  1         35  
5 1     1   5 use warnings;
  1         7  
  1         64  
6              
7 1     1   1993 use AutoLoader qw(AUTOLOAD);
  1         1837  
  1         6  
8              
9 1     1   36 use Carp;
  1         2  
  1         98  
10 1     1   7206 use CGI;
  1         36021  
  1         8  
11 1     1   2553 use DBI;
  1         24934  
  1         86  
12 1     1   1260 use JSON::XS;
  1         7816  
  1         1208  
13              
14             our $VERSION = '0.03';
15              
16             # Preloaded methods go here.
17              
18             # Autoload methods go after =cut, and are processed by the autosplit program.
19              
20             sub new {
21 0     0 1   my $invocant = shift;
22 0   0       my $class = ref($invocant) || $invocant;
23 0           my $self = {
24             tables => undef,
25             columns => undef,
26             user => undef,
27             pass => undef,
28             db => undef,
29             host => "localhost",
30             port => "3306",
31             patterns => {},
32             join_clause => '',
33             where_clause => '',
34             index_col => "id",
35             index_cols => undef,
36             @_, # Override previous attributes
37             };
38 0           return bless $self, $class;
39             }
40              
41             sub tables {
42 0     0 1   my $self = shift;
43            
44 0 0         if (@_) {
45 0           my $a_ref = shift;
46 0 0         croak "tables must be an array ref" unless UNIVERSAL::isa($a_ref,'ARRAY');
47 0           $self->{tables} = $a_ref;
48             }
49 0           return $self->{tables};
50             }
51              
52             sub columns {
53 0     0 1   my $self = shift;
54            
55 0 0         if (@_) {
56 0           my $ref = shift;
57 0 0 0       croak "columns_a must be an array or hash ref" unless UNIVERSAL::isa($ref,'ARRAY') or UNIVERSAL::isa($ref,'HASH');
58 0           $self->{columns} = $ref;
59             }
60 0           return $self->{columns};
61             }
62              
63             sub index_cols {
64 0     0 1   my $self = shift;
65            
66 0 0         if (@_) {
67 0           my $h_ref = shift;
68 0 0         croak "index_cols must be a hash ref" unless UNIVERSAL::isa($h_ref,'HASH');
69 0           $self->{index_cols} = $h_ref;
70             }
71 0           return $self->{index_cols};
72             }
73              
74             sub patterns {
75 0     0 1   my $self = shift;
76            
77 0 0         if (@_) {
78 0           my $h_ref = shift;
79 0 0         croak "patterns must be a hash ref" unless UNIVERSAL::isa($h_ref,'HASH');
80 0           $self->{patterns} = $h_ref;
81             }
82 0           return $self->{patterns};
83             }
84              
85             sub user {
86 0     0 1   my $self = shift;
87            
88 0 0         if (@_) {
89 0           $self->{user} = shift;
90             }
91 0           return $self->{user};
92             }
93              
94             sub pass {
95 0     0 1   my $self = shift;
96            
97 0 0         if (@_) {
98 0           $self->{pass} = shift;
99             }
100 0           return $self->{pass};
101             }
102              
103             sub db {
104 0     0 1   my $self = shift;
105            
106 0 0         if (@_) {
107 0           $self->{db} = shift;
108             }
109 0           return $self->{db};
110             }
111              
112             sub host {
113 0     0 1   my $self = shift;
114            
115 0 0         if (@_) {
116 0           $self->{host} = shift;
117             }
118 0           return $self->{host};
119             }
120              
121             sub port {
122 0     0 1   my $self = shift;
123            
124 0 0         if (@_) {
125 0           $self->{port} = shift;
126             }
127 0           return $self->{port};
128             }
129              
130             sub join_clause {
131 0     0 1   my $self = shift;
132            
133 0 0         if (@_) {
134 0           $self->{join_clause} = shift;
135             }
136 0           return $self->{join_clause};
137             }
138              
139             sub where_clause {
140 0     0 1   my $self = shift;
141            
142 0 0         if (@_) {
143 0           $self->{where_clause} = shift;
144             }
145 0           return $self->{where_clause};
146             }
147              
148             sub index_col {
149 0     0 1   my $self = shift;
150            
151 0 0         if (@_) {
152 0           $self->{index_col} = shift;
153             }
154 0           return $self->{index_col};
155             }
156              
157             #for some values in the query we build we can't use
158             #placeholders because they add quotes when they shouldn't.
159             #So here we use $dbh->quote() to escape these string then we remove
160             # the ''. Not as good as placeholders, but we need something
161             sub _special_quote {
162 0     0     my ($dbh,$string) = (@_);
163 0           my $ns = $dbh->quote($string);
164 0           $ns = substr $ns, 1;
165 0           $ns= substr $ns, 0,-1;
166 0           return $ns;
167             }
168              
169             sub _columns_arr {
170 0     0     my $self = shift;
171 0           my $aColumns;
172             my $regular_columns;
173 0           my $as_hash;
174 0           my $tables_hash;
175              
176 0 0         if(UNIVERSAL::isa($self->columns,'HASH')) {
    0          
177 0           my $columns = $self->columns;
178              
179 0           for my $key (sort {$a <=> $b} keys %{$columns}) { #here we sort by key so columns show in the same order as they on the page
  0            
  0            
180 0           my $as_exists = undef;
181              
182             #if two keys, we assume user passed in AS as a key. We could check for as below in loop, but that limits users from having a column named AS
183 0 0 0       if(scalar(keys %{$columns->{$key}} == 2) and exists $columns->{$key}->{'AS'}) {
  0            
184 0           $as_exists = $columns->{$key}->{'AS'};
185 0           delete $columns->{$key}->{'AS'};
186             }
187              
188 0           while(my ($column,$table) = each %{$columns->{$key}}) {
  0            
189 0           my $column_name = "$table.$column";
190 0           push @{$aColumns}, $column_name;
  0            
191            
192 0 0         if($as_exists) {
193 0 0         $as_hash->{$column_name} = $as_exists if $as_exists; #add 'AS' value for this column if one exists
194 0           $column = $as_exists; # we want to change the column name to what it will be selected as out of database so we can do correct pattern matching
195             }
196              
197 0           $tables_hash->{$table} = 1;
198 0           push @{$regular_columns}, $column;
  0            
199             }
200             }
201              
202 0           my @tables = keys %$tables_hash;
203 0           $self->tables(\@tables);
204             }
205             elsif(UNIVERSAL::isa($self->columns,'ARRAY')) {
206 0           $aColumns = $self->columns;
207 0           $regular_columns = $aColumns;
208             }
209             else {
210 0           croak "columns must be a hash or an array ref";
211             }
212              
213 0           return ($aColumns,$regular_columns,$as_hash);
214             }
215              
216             sub print_json {
217 0     0 1   my $self = shift;
218 0           my $json = $self->json;
219              
220 0           print "Content-type: application/json\n\n";
221 0           print $json;
222             }
223              
224             sub json {
225 1     1   9 use strict;
  1         2  
  1         1397  
226 0     0 1   my $self = shift;
227              
228             # CGI OBJECT
229 0           my $q = new CGI;
230              
231             # DB CONFIG VARIABLES
232 0           my $platform = "mysql";
233 0           my $database = $self->{db};
234 0           my $host = $self->{host};
235 0           my $port = $self->{port};
236 0           my $user = $self->{user};
237 0           my $pw = $self->{pass};
238              
239             #DATA SOURCE NAME
240 0           my $dsn = "dbi:mysql:$database:$host:3306";
241              
242             # get database handle
243 0 0         my $dbh = DBI->connect($dsn, $user, $pw) or croak "couldn't connect to database: $!";
244              
245             #columns to use
246 0           my ($aColumns,$regular_columns,$as_hash) = $self->_columns_arr;
247              
248             #this bind array is used for secure database queries
249             #in an effort to help prevent sql injection
250 0           my @bind = ();
251              
252 0 0         croak "Tables must be provided for the FROM clause" unless $self->tables;
253 0           my $sTable = join ",",@{$self->tables};
  0            
254              
255             #filtering
256 0           my $sWhere = "";
257 0 0         if ($q->param('sSearch') ne '') {
258 0           $sWhere = "WHERE (";
259            
260 0           for(my $i = 0; $i < @$aColumns; $i++) {
261 0           my $search = $q->param('sSearch');
262 0           $search = _special_quote($dbh,$search);
263 0           $sWhere .= "" . $aColumns->[$i] . " LIKE '%$search%' OR ";
264             }
265            
266 0           $sWhere = substr $sWhere,0,-3;
267 0           $sWhere .= ')';
268             }
269              
270             #individual column filtering
271 0           for (my $i = 0; $i < @$aColumns; $i++) {
272 0 0 0       if($q->param('bSearchable_' . $i) ne '' and $q->param('bSearchable_' . $i) eq "true" and $q->param('sSearch_' . $i) ne '') {
      0        
273 0 0         if($sWhere eq "") {
274 0           $sWhere = "WHERE ";
275             }
276             else {
277 0           $sWhere .= " AND ";
278             }
279 0           my $search = $q->param('sSearch_' . $i);
280 0           $search = _special_quote($dbh,$search);
281 0           $sWhere .= "" . $aColumns->[$i] . " LIKE '%$search%' ";
282             }
283             }
284              
285             # add user where if given
286 0 0         if($self->where_clause ne '') {
287 0 0         if($sWhere eq "") {
288 0           $sWhere = "WHERE ";
289             }
290             else {
291 0           $sWhere .= " AND ";
292             }
293 0           $sWhere .= " " . $self->where_clause . " ";
294             }
295              
296             #ordering
297 0           my $sOrder = "";
298 0 0         if($q->param('iSortCol_0') ne '') {
299 0           $sOrder = "ORDER BY ";
300            
301 0           for(my $i = 0; $i < $q->param('iSortingCols'); $i++) {
302 0 0         if($q->param('bSortable_' . $q->param('iSortCol_'.$i)) eq "true") {
303 0           my $sort_col = $aColumns->[$q->param('iSortCol_' . $i)];
304 0           my $sort_dir = $q->param('sSortDir_' . $i);
305            
306             # cannot use bind because bind puts '' around values.
307             # backslash out quotes
308 0           $sort_col = _special_quote($dbh,$sort_col);
309 0           $sort_dir = _special_quote($dbh,$sort_dir);
310            
311 0           $sOrder .= "" . $sort_col . " " . $sort_dir . ", ";
312             }
313             }
314            
315 0           $sOrder = substr $sOrder,0,-2;
316 0 0         if( $sOrder eq "ORDER BY" ) {
317 0           $sOrder = "";
318             }
319             }
320              
321             #paging
322 0           my $sLimit = "";
323 0 0 0       if ($q->param('iDisplayStart') ne '' and $q->param('iDisplayLength') ne '-1') {
324 0           $sLimit = "LIMIT ?,? ";
325 0           push @bind,$q->param('iDisplayStart');
326 0           push @bind,$q->param('iDisplayLength');
327             }
328              
329             #join
330 0           my $sJoin = '';
331 0 0         if($self->join_clause ne '') {
332 0 0         if($sWhere ne '') {
    0          
333 0           $sJoin .= ' AND ';
334             }
335             elsif($sWhere eq '') {
336 0           $sWhere = ' WHERE ';
337             }
338 0           $sJoin .= ' ' . $self->join_clause . ' ';
339             }
340              
341             #SQL queries
342             #get data to display
343 0           my $cols = join ", ", @{$aColumns};
  0            
344 0           my $sQuery = "SELECT SQL_CALC_FOUND_ROWS " . $cols . " FROM $sTable $sWhere $sJoin $sOrder $sLimit ";
345              
346             #get columns out of db with query we created
347 0           my $result_sth = $dbh->prepare($sQuery);
348 0 0         $result_sth->execute(@bind) or croak "error in mysql query: $!\n$sQuery";
349              
350             # Data set length after filtering
351 0           $sQuery = " SELECT FOUND_ROWS() ";
352            
353 0           my $sth = $dbh->prepare($sQuery);
354 0 0         $sth->execute() or croak "mysql error: $!";
355              
356 0           my @aResultFilterTotal = $sth->fetchrow_array();
357 0           my $iFilteredTotal = $aResultFilterTotal[0];
358              
359 0           my $iTotal = 0;
360              
361 0           my $num_tables = scalar(@{$self->tables});
  0            
362 0           my $index_h = $self->index_cols;
363              
364 0           for my $table(@{$self->tables}) {
  0            
365 0           my $sIndexColumn = '';
366 0 0         if($num_tables == 1) {
367 0           $sIndexColumn = $self->index_col;
368             }
369             else {
370 0           $sIndexColumn = $index_h->{$table};
371 0 0         $sIndexColumn = "id" unless $sIndexColumn;
372             }
373             # Total data set length
374 0           $sQuery = " SELECT COUNT(`" . $sIndexColumn . "`) FROM $table ";
375              
376 0           $sth = $dbh->prepare($sQuery);
377 0 0         $sth->execute() or croak "error in query: $!\n$sQuery\nMost likely related to index columns passed in";
378              
379 0           my @aResultTotal = $sth->fetchrow_array;
380 0           $iTotal += $aResultTotal[0];
381             }
382              
383             # output hash
384 0           my %output = (
385             "sEcho" => $q->param('sEcho'),
386             "iTotalRecords" => $iTotal,
387             "iTotalDisplayRecords" => $iFilteredTotal,
388             "aaData" => ()
389             );
390              
391 0           my $count = 0;
392 0           my $patterns = $self->patterns;
393 0           while(my @aRow = $result_sth->fetchrow_array) {
394 0           my @row = ();
395 0           for (my $i = 0; $i < @$aColumns; $i++) {
396 0           my $pat_name = $regular_columns->[$i]; #get out the name that would be used in the pattern
397 0           my $val = $aRow[$i];
398              
399             # apply user specified pattern for this column if one exists
400 0 0         if(exists $patterns->{$pat_name}) {
401 0           my $pattern = $patterns->{$pat_name};
402 0           $pattern =~ s/\[\%\s$pat_name\s\%\]/$val/g;
403 0           $val = $pattern;
404             }
405              
406 0           push @row, $val;
407             }
408 0           @{$output{'aaData'}}[$count] = [@row];
  0            
409 0           $count++;
410             }
411              
412 0 0         unless($count) {
413 0           $output{'aaData'} = ''; #we don't want to have 'null'. will break js
414             }
415              
416 0           return encode_json \%output;
417             }
418              
419             1;
420             __END__