1 # Copyright 2002-2007 Interchange Development Group and others
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version. See the LICENSE file for details.
8 # $Id: tree.coretag,v 1.12 2007-07-18 00:16:26 jon Exp $
10 UserTag tree Order table master subordinate start
12 UserTag tree attrAlias sub subordinate
13 UserTag tree hasEndTag
14 UserTag tree Version $Revision: 1.12 $
15 UserTag tree Routine <<EOR
17 my($table, $parent, $sub, $start_item, $opt, $text) = @_;
19 #::logDebug("tree-list: received parent=$parent sub=$sub start=$start_item");
25 my $delim = $opt->{delimiter} || "\t";
26 my $s = $opt->{subordinate} || 'code';
27 my $l = $opt->{level_field} || 'msort';
29 my @lines = split /\n/, readfile($opt->{file});
30 my $hdr = shift @lines;
31 my @fields = split $delim, $hdr;
35 @{$ref}{@fields} = split $delim, $_;
38 push @start, $ref if $ref->{$l} == 0;
45 $db = ::database_exists_ref($table)
46 or return error_opt($opt, "Database %s doesn't exist", $table);
47 $db->column_exists($parent)
48 or return error_opt($opt, "Parent column %s doesn't exist", $parent);
49 $db->column_exists($sub)
50 or return error_opt($opt, "Subordinate column %s doesn't exist", $sub);
58 if($opt->{multiple_start}) {
59 @keys = split /[\0,\s]+/, $start_item;
67 push @things, "$parent = " . $db->quote($_, $parent);
70 $basewhere = join " OR ", @things;
74 if(defined $opt->{outline}) {
75 $opt->{outline} =~ s/[^a-zA-Z0-9]+//g;
76 @outline = split //, $opt->{outline};
77 @outline = (qw/1 A 1 a 1 a/) if scalar @outline < 2;
80 my $mult = ( int($opt->{spacing}) || 10 );
82 $keyfield = $db->config('KEY') unless $nodb;
83 $opt->{code_field} = $keyfield if ! $opt->{code_field};
89 unless $opt->{sort} =~ /^\s*order\s+by\s+/i;
91 @sort = ref $opt->{sort}
95 s/\s*[=:]\s*([rnxf]).*//;
96 $_ .= " DESC" if $1 eq 'r';
98 $sort .= join ", ", @sort;
104 if( my $f = $db->config('HIDE_FIELD')) {
105 $where .= " AND $f <> 1";
110 $where .= " AND ($opt->{where})";
113 my $qb = "SELECT * FROM $table WHERE $basewhere$where$sort";
114 #::logDebug("tree tag initial query=$qb");
129 $memo = ($::Scratch->{$opt->{memo}} ||= {});
131 if($opt->{toggle} and $toggle = $CGI::values{$opt->{toggle}}) {
132 $memo->{$toggle} = ! $memo->{$toggle};
136 if($opt->{collapse} and $CGI::values{$opt->{collapse}}) {
138 delete $::Scratch->{$opt->{memo}} if $opt->{memo};
142 if($opt->{full} or $opt->{explode} and $CGI::values{$opt->{explode}}) {
152 $dbh = $db->dbh() unless $nodb;
154 my $qs_query = "SELECT * FROM $table WHERE $parent = ?$where$sort";
156 my $l = $opt->{level_field} || 'msort';
157 #::logDebug("setting up nodb qsub level=$l");
160 #::logDebug("Looking for key=$key");
162 my $base = $passed[$key - 1]->{$l} + 1;
163 #::logDebug("Base level=$base, firstone = $passed[$key]{$l}");
165 for(my $i = $key; $passed[$i]{$l} >= $base ; $i++ ) {
166 push @out, $passed[$i] if $passed[$i]{$l} == $base;
172 elsif($dbh and $db->config('Class') eq 'DBI') {
173 my $sth = $dbh->prepare($qs_query)
175 "tree failed to prepare query: %s\nError was: %s",
180 #::logDebug("executing query sub DBI style"); # while ! $donemsg++;
185 "tree failed to prepare query for '%s': %s\nError was: %s",
190 while(my $ref = $sth->fetchrow_hashref()) {
191 push @ary, { %$ref };
200 #::logDebug("executing query sub regular style"); # while ! $donemsg++;
201 $parm = $db->quote($parm, $parent);
203 $q =~ s/\s\?\s/ $parm /;
204 $db->query( { hashref => 1, sql => $q });
209 $memo = {} if ! $memo;
215 #::logDebug("tree-list: valid parent=$parent sub=$sub start=$start_item mult=$mult");
217 my @ary_stack = ( $ary ); # Stacks the rows
218 my @above_stack = { $start_item => 1 }; # Holds the previous levels
219 my @inc_stack = ($outline[0]); # Holds the increment characters
224 #::logDebug("next ary");
225 my $ary = pop(@ary_stack)
227 my $above = pop(@above_stack);
228 my $level = scalar(@ary_stack);
229 my $increment = pop(@inc_stack);
231 #::logDebug("next row level=$level increment=$increment");
234 or ($prev and $prev->{mv_last} = 1), last ROW;
235 $row->{mv_level} = $level;
236 $row->{mv_spacing} = $level * $mult;
237 $row->{mv_spacer} = $opt->{spacer} x $row->{mv_spacing}
239 $row->{mv_increment} = $increment++;
240 $row->{mv_ip} = $count++;
242 my $code = $row->{$keyfield};
243 $row->{mv_toggled} = 1 if $memo->{$code};
244 #::logDebug("next row sub=$sub=$row->{$sub}");
245 my $next = $row->{$sub}
249 $row->{mv_children} = 1
250 if ($opt->{stop} and ! $row->{ $opt->{stop} } )
251 or ($opt->{continue} and $row->{ $opt->{continue} })
252 or ($opt->{autodetect});
254 $stop = 1 if ! $explode and ! $memo->{$code};
255 #::logDebug("next row sub=$sub=$next stop=$stop explode=$explode memo=$memo->{$code}");
257 if($above->{$next} and ($opt->{autodetect} or ! $stop) ) {
259 Endless tree detected at key %s in table %s.
260 Parent %s, would traverse to %s.
262 my $msg = ::errmsg($fmt, $code, $table, $row->{$parent}, $next);
263 if(! $opt->{pedantic}) {
264 error_opt($opt, $msg);
268 $opt->{log_error} = 1 unless $opt->{show_error};
269 return error_opt($opt, $msg);
274 if ($opt->{autodetect} or ! $stop) {
275 #::logDebug("next=$next row query=$q");
277 $above->{$next} = 1 if $a and scalar @{$a};
280 if($opt->{autodetect}) {
281 $row->{mv_children} = $a ? scalar(@$a) : 0;
285 push(@ary_stack, $ary);
286 push(@above_stack, $above);
287 push(@inc_stack, $increment);
289 $increment = defined $outline[$level] ? $outline[$level] : 1;
293 #::logDebug("last row");
295 $opt->{object} = { mv_results => \@rows };
296 #::logDebug("last ary, results =" . ::uneval(\@rows));
297 return labeled_list($opt, $text, $opt->{object});