* Add enclair_db option to UserDB.pm. Allows logging of enclair password
[interchange.git] / lib / Vend / Menu.pm
1 # Vend::Menu - Interchange menu processing routines
2 #
3 # $Id: Menu.pm,v 2.53 2009-02-24 15:29:01 jon Exp $
4 #
5 # Copyright (C) 2002 Mike Heins, <mike@perusion.net>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public
18 # License along with this program; if not, write to the Free
19 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
20 # MA  02110-1301  USA.
21
22 package Vend::Menu;
23
24 $VERSION = substr(q$Revision: 2.53 $, 10);
25
26 use Vend::Util;
27 use strict;
28 no warnings qw(uninitialized numeric);
29
30 my $indicated;
31 my $last_line;
32 my $first_line;
33 my $logical_field;
34
35 my %transform = (
36         nbsp => sub {
37                 my ($row, $fields) = @_;
38                 return 1 if ref($fields) ne 'ARRAY';
39                 for(@$fields) {
40                         $row->{$_} =~ s/ /&nbsp;/g;
41                 }
42                 return 1;
43         },
44         entities => sub {
45                 my ($row, $fields) = @_;
46                 return 1 if ref($fields) ne 'ARRAY';
47                 for(@$fields) {
48                         $row->{$_} = HTML::Entities::encode_entities($row->{$_});
49                 }
50                 return 1;
51         },
52         localize => sub {
53                 my ($row, $fields) = @_;
54                 return 1 if ref($fields) ne 'ARRAY';
55                 for(@$fields) {
56                         $row->{$_} = errmsg($row->{$_});
57                 }
58                 return 1;
59         },
60         first_line => sub {
61                 my ($row, $fields) = @_;
62                 return undef if ref($fields) ne 'ARRAY';
63                 return 1 if $first_line;
64                 my $status;
65                 for(@$fields) {
66                         if(s/^!\s*//) {
67                                 $status = $status && ! $row->{$_};
68                         }
69                         else {
70                                 $status = $status && $row->{$_};
71                         }
72                 }
73                 return $first_line = $status;
74         },
75         last_line => sub {
76                 my ($row, $fields) = @_;
77 #::logDebug("last_line transform, last_line=$last_line");
78                 return 1 if ref($fields) ne 'ARRAY';
79                 return 0 if $last_line;
80                 my $status;
81                 for(@$fields) {
82 #::logDebug("last_line transform checking field $_=$row->{$_}");
83                         if(s/^!\s*//) {
84                                 $status = ! $row->{$_};
85                         }
86                         else {
87                                 $status = $row->{$_};
88                         }
89 #::logDebug("last_line transform checked field $_=$row->{$_}, status=$status");
90                         last if $status;
91                 }
92 #::logDebug("last_line transform returning last_line=$status");
93                 $last_line = $status;
94 #::logDebug("last_line transform returning status=" . ! $status);
95                 return ! $status;
96         },
97         first_line => sub {
98                 my ($row, $fields) = @_;
99                 return 1 if ref($fields) ne 'ARRAY';
100                 my $status = 1;
101                 for(@$fields) {
102                         if(s/^!\s*//) {
103                                 $status = $status && ! $row->{$_};
104                         }
105                         else {
106                                 $status = $status && $row->{$_};
107                         }
108                 }
109                 return $status;
110         },
111         inactive => sub {
112                 my ($row, $fields) = @_;
113                 return 1 if ref($fields) ne 'ARRAY';
114                 my $status = 1;
115                 for(@$fields) {
116                         if(s/^!\s*//) {
117                                 $status = $status && $row->{$_};
118                         }
119                         else {
120                                 $status = $status && ! $row->{$_};
121                         }
122                 }
123                 return $status;
124         },
125         active => sub {
126                 my ($row, $fields) = @_;
127                 return 1 if ref($fields) ne 'ARRAY';
128                 my $status = 1;
129                 for(@$fields) {
130                         if(s/^!\s*//) {
131                                 $status = $status && ! $row->{$_};
132                         }
133                         else {
134                                 $status = $status && $row->{$_};
135                         }
136                 }
137                 return $status;
138         },
139         ui_security => sub {
140                 my ($row, $fields) = @_;
141                 return 1 if ref($fields) ne 'ARRAY';
142                 my $status = 1;
143                 for(@$fields) {
144                         next if ! length($row->{$_});
145                         $status = $status && Vend::Tags->if_mm('advanced', $row->{$_});
146                 }
147                 return $status;
148         },
149         full_interpolate => sub {
150                 my ($row, $fields) = @_;
151                 return 1 if ref($fields) ne 'ARRAY';
152                 for(@$fields) {
153                         next unless $row->{$_} =~ /\[|__[A-Z]\w+__/;
154                         $row->{$_} = Vend::Interpolate::interpolate_html($row->{$_});
155                 }
156                 return 1;
157         },
158         page_class => sub {
159                 my ($row, $fields) = @_;
160                 return 1 unless $row->{indicated};
161                 return 1 if $row->{mv_level};
162                 return 1 if ref($fields) ne 'ARRAY';
163                 my $status = 1;
164                 for(@$fields) {
165                         my($f, $c) = split /[=~]+/, $_;
166                         $c ||= $f;
167 #::logDebug("setting scratch $f to row=$c=$row->{$c}");
168                         $::Scratch->{$f} = $row->{$c};
169                 }
170                 $$indicated = 0;
171                 return 1;
172         },
173         menu_group => sub {
174                 my ($row, $fields) = @_;
175                 return 1 if ref($fields) ne 'ARRAY';
176                 my $status = 1;
177                 eval {
178                         for(@$fields) {
179                                 my($f, $c) = split /[=~]+/, $_;
180                                 $c ||= $f;
181                                 $status = $status && (
182                                                                 !  $row->{$f}
183                                                                 or $CGI::values{$c} =~ /$row->{$f}/i
184                                                                 );
185                         }
186                 };
187                 return $status;
188         },
189         superuser => sub {
190                 my ($row, $fields) = @_;
191                 return 1 if ref($fields) ne 'ARRAY';
192                 my $status = 1;
193                 for(@$fields) {
194                         $status = $status && (! $row->{$_} or Vend::Tags->if_mm('super'));
195                 }
196                 return $status;
197         },
198         items   => sub {
199                 my ($row, $fields) = @_;
200                 return 1 if ref($fields) ne 'ARRAY';
201                 my $status = 1;
202                 my $nitems = scalar(@{$Vend::Items}) ? 1 : 0;
203                 for(@$fields) {
204                         next if ! length($row->{$_});
205                         $status = $status && (! $nitems ^ $row->{$_});
206                 }
207                 return $status;
208         },
209         logged_in => sub {
210                 my ($row, $fields) = @_;
211                 return 1 if ref($fields) ne 'ARRAY';
212                 my $status = 1;
213                 for(@$fields) {
214                         next if ! length($row->{$_});
215                         $status = $status && (! $::Vend::Session->{logged_in} ^ $row->{$_});
216                 }
217                 return $status;
218         },
219         depends_on => sub {
220                 my ($row, $fields) = @_;
221                 return 1 if ref($fields) ne 'ARRAY';
222                 my $status = 1;
223                 for(@$fields) {
224                         next if ! $row->{$_};
225                         $status = $status && $CGI::values{$row->{$_}};
226                 }
227                 return $status;
228         },
229         exclude_on => sub {
230                 my ($row, $fields) = @_;
231                 return 1 if ref($fields) ne 'ARRAY';
232                 my $status = 1;
233                 for(@$fields) {
234                         $status = $status && (! $CGI::values{$row->{$_}});
235                 }
236                 return $status;
237         },
238         indicator_class => sub {
239                 my ($row, $fields) = @_;
240                 return 1 if ref($fields) ne 'ARRAY';
241                 for(@$fields) {
242                         my ($indicator,$rev, $last, $status);
243                         my($s,$r) = split /=/, $_;
244                         $rev = $indicator =~ s/^\s*!\s*// ? 1 : 0;
245                         $last = $indicator =~ s/\s*!\s*$// ? 1 : 0;
246 #::logDebug("checking scratch $s=$::Scratch->{$s} eq row=$r=$row->{$r}");
247                         $status = $::Scratch->{$s} eq $row->{$r};
248                         if($rev xor $status) {
249                                 $row->{indicated} = 1;
250                         }
251                         last if $last;
252                 }
253                 if($row->{indicated}) {
254                         $indicated = \$row->{indicated};
255                 }
256                 return 1;
257         },
258         indicator_profile => sub {
259                 my ($row, $fields) = @_;
260                 return 1 if ref($fields) ne 'ARRAY';
261                 for(@$fields) {
262                         my ($indicator,$rev, $last, $status);
263                         next unless $indicator = $row->{$_};
264                         $rev = $indicator =~ s/^\s*!\s*// ? 1 : 0;
265                         $last = $indicator =~ s/\s*!\s*$// ? 1 : 0;
266                         $status = Vend::Tags->run_profile($indicator);
267                         if($rev xor $status) {
268                                 $row->{indicated} = 1;
269                                 next unless $last;
270                         }
271                         last if $last;
272                 }
273                 return 1;
274         },
275         indicator_page => sub {
276                 my ($row, $fields) = @_;
277                 return 1 if ref($fields) ne 'ARRAY';
278                 for(@$fields) {
279                         if ($::Scratch->{mv_logical_page} eq $row->{$_}) {
280                                 unless(
281                                                 $::Scratch->{mv_logical_page_used}
282                                                 and $::Scratch->{mv_logical_page_used}
283                                                           ne
284                                                         $row->{$logical_field}
285                                                 )
286                                 {
287                                         $row->{indicated} = 1;
288                                         $::Scratch->{mv_logical_page_used} = $row->{$logical_field};
289                                         last;
290                                 }
291                         }
292                         ($row->{indicated} = 1, last)
293                                 if  $Global::Variable->{MV_PAGE} eq $row->{$_}
294                                 and ! defined $row->{indicated};
295                 }
296                 return 1;
297         },
298         indicator => sub {
299                 my ($row, $fields) = @_;
300                 return 1 if ref($fields) ne 'ARRAY';
301                 for(@$fields) {
302                         my ($indicator,$rev, $last, $status);
303                         next unless $indicator = $row->{$_};
304                         $rev = $indicator =~ s/^\s*!\s*// ? 1 : 0;
305                         $last = $indicator =~ s/\s*!\s*$// ? 1 : 0;
306                         if($indicator =~ /^\s*([-\w.:][-\w.:]+)\s*$/) {
307                                 $status =  $CGI::values{$1};
308                         }
309                         elsif ($indicator =~ /^\s*`(.*)`\s*$/s) {
310                                 $status = Vend::Interpolate::tag_calc($1);
311                         }
312                         elsif ($indicator =~ /\[/s) {
313                                 $status = Vend::Interpolate::interpolate_html($indicator);
314                                 $status =~ s/\s+//g;
315                         }
316                         if($rev xor $status) {
317                                 $row->{indicated} = 1;
318                         }
319                         else {
320                                 $row->{indicated} = '';
321                         }
322                         last if $last;
323                 }
324                 return 1;
325         },
326         expand_values_form => sub {
327                 my ($row, $fields) = @_;
328                 return 1 if ref($fields) ne 'ARRAY';
329                 for(@$fields) {
330                         next unless $row->{$_} =~ /\%5b|\[/i;
331                         my @parms = split $Global::UrlSplittor, $row->{$_};
332                         my @out;
333                         for my $p (@parms) {
334                                 my ($parm, $val) = split /=/, $p, 2;
335                                 $val = unhexify($val);
336                                 $val =~ s/\[cgi\s+([^\[]+)\]/$CGI::values{$1}/g;
337                                 $val =~ s/\[var\s+([^\[]+)\]/$::Variable->{$1}/g;
338                                 $val =~ s/\[value\s+([^\[]+)\]/$::Values->{$1}/g;
339                                 push @out, join('=', $parm, hexify($val));
340                         }
341                         $row->{$_} = join $Global::UrlJoiner, @out;
342                 }
343                 return 1;
344         },
345         expand_values => sub {
346                 my ($row, $fields) = @_;
347                 return 1 if ref($fields) ne 'ARRAY';
348                 for(@$fields) {
349                         next unless $row->{$_} =~ /\[/;
350                         $row->{$_} =~ s/\[cgi\s+([^\[]+)\]/$CGI::values{$1}/g;
351                         $row->{$_} =~ s/\[var\s+([^\[]+)\]/$::Variable->{$1}/g;
352                         $row->{$_} =~ s/\[value\s+([^\[]+)\]/$::Values->{$1}/g;
353                 }
354                 return 1;
355         },
356 );
357
358 sub extra_value {
359         my ($extra, $row) = @_;
360         if(ref($extra) ne 'HASH') {
361                 my ($k, $v) = split /=/, $extra, 2;
362                 $extra = { $k => $v };
363         }
364
365         for(keys %$extra) {
366                 $row->{$_} = $extra->{$_}
367                         if length($extra->{$_});
368         }
369         return;
370 }
371
372 sub reset_transforms {
373 #::logDebug("resetting transforms");
374         my $opt = shift;
375         if($opt) {
376                 $logical_field = $opt->{logical_page_field} || 'name';
377         }
378         undef $last_line;
379         undef $first_line;
380         undef $indicated;
381 }
382
383 sub old_tree {
384         my ($name, $opt, $template) = @_;
385         my @out;
386         my $u;
387         if(! $opt->{explode_url}) {
388                 $u = Vend::Tags->history_scan( { var_exclude => 'toggle,collapse,expand' });
389                 $opt->{explode_url} = $u;
390                 $opt->{explode_url} .= $u =~ /\?/ ? $Global::UrlJoiner : "?";
391                 $opt->{explode_url} .= 'explode=1';
392         }
393         if(! $opt->{collapse_url}) {
394                 $u ||= Vend::Tags->history_scan( { var_exclude => 'toggle,collapse,expand' });
395                 $opt->{collapse_url} = $u;
396                 $opt->{collapse_url} .= $u =~ /\?/ ? $Global::UrlJoiner : "?";
397                 $opt->{collapse_url} .= 'collapse=1';
398         }
399
400         my $explode_label = errmsg($opt->{explode_label} || 'Explode tree');
401         my $collapse_label = errmsg($opt->{collapse_label} || 'Collapse tree');
402
403         $opt->{header_template} ||= <<EOF;
404 <p>
405 <a href="{EXPLODE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$explode_label</a><br$Vend::Xtrailer>
406 <a href="{COLLAPSE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$collapse_label</a>
407 </p>
408 EOF
409
410         my $header;
411         $header = ::interpolate_html($opt->{header_template})
412                 if $opt->{header_template};
413         if($header =~ /\S/) {
414                 $header = Vend::Tags->uc_attr_list($opt, $header);
415                 push @out, $header;
416         }
417
418         my %defaults = (
419                                 start       => $opt->{tree_selector} || 'Products',
420                                 table       => $::Variable->{MV_TREE_TABLE} || 'tree',
421                                 master      => $opt->{tree_master} || 'parent_fld',
422                                 subordinate => 'code',
423                                 autodetect  => '1',
424                                 sort        => 'code',
425                                 iterator    => \&tree_link,
426                                 spacing     => '4',
427                                 toggle      => 'toggle',
428                                 memo        => 'memo',
429                                 expand      => 'expand',
430                                 collapse    => 'collapse',
431                                 spacer          => '&nbsp;',
432                         );
433
434         while( my ($k, $v) = each %defaults) {
435                 next if defined $opt->{$k};
436                 $opt->{$k} = $v;
437         }
438         push @out, Vend::Tags->tree($opt);
439
440         my $footer;
441         $footer = ::interpolate_html($opt->{footer_template})
442                 if $opt->{footer_template};
443         if($footer =~ /\S/) {
444                 $footer = Vend::Tags->uc_attr_list($opt, $footer);
445                 push @out, $footer;
446         }
447
448         return join "\n", @out;
449
450 }
451
452 sub old_simple {
453         my ($name, $opt, $template) = @_;
454         my @out;
455         my $u;
456
457         my %defaults = (
458                                 head_skip   => 1,
459                         );
460
461         while( my ($k, $v) = each %defaults) {
462                 next if defined $opt->{$k};
463                 $opt->{$k} = $v;
464         }
465
466         my $iterator;
467
468         my $main;
469         if($opt->{iterator}) {
470                 $main = Vend::Tags->loop(undef,$opt,$template);
471         }
472         else {
473                 $opt->{iterator} = \&transforms_only;
474                 delete $opt->{_transforms};
475                 Vend::Tags->loop(undef,$opt,'');
476                 reset_transforms();
477                 my $list = $opt->{object}{mv_results};
478                 if(@$list and my $fn = $opt->{object}{mv_field_names}) {
479                         push @$fn, 'mv_last_row';
480                         $list->[-1][$#$fn] = 1;
481                 }
482                 $main = join($opt->{joiner}, map {menu_link($template, $_, $opt)} @$list);
483         }
484
485         # Prevent possibility of memory leak
486         reset_transforms();
487
488         my $header;
489         $header = ::interpolate_html($opt->{header_template})
490                 if $opt->{header_template};
491         if($header =~ /\S/) {
492                 push @out, Vend::Tags->uc_attr_list($opt, $header);
493         }
494
495         push @out, $main;
496
497         my $footer;
498
499         $footer = ::interpolate_html($opt->{footer_template})
500                 if $opt->{footer_template};
501         if($footer =~ /\S/) {
502                 push @out, Vend::Tags->uc_attr_list($opt, $footer);
503         }
504
505         return join "\n", @out;
506
507 }
508
509 sub dhtml_simple {
510         return old_simple(@_);
511 }
512
513 sub old_flyout {
514         return dhtml_flyout(@_);
515 }
516
517 sub dhtml_flyout {
518         my($name, $opt, $template) = @_;
519
520         my @out;
521         my $fdiv = $name . "_flyout";
522
523         my $vpf = $opt->{js_prefix} ||= 'mv_';
524
525         $template = <<EOF if $template !~ /\S/;
526 {MV_LEVEL:}<div>{PAGE?}{MV_SPACER}<a id="{CODE}" href="{PAGE}" onMouseOver="${vpf}mousein(this)" onMouseOut="${vpf}mouseout(this)" title="{DESCRIPTION}" class="$opt->{link_class}">{NAME}</a>{/PAGE?}{PAGE:}{MV_SPACER}{NAME}{/MV_SPACER}{/PAGE:}</div>{/MV_LEVEL:}
527 EOF
528
529         $opt->{cursor_type} ||= 'hand';
530         $opt->{flyout_style} ||= <<EOF;
531                 font-weight: bold;
532                 text-align: left;
533                 font-size: 10px;
534                 font-family: verdana,arial;
535                 cursor: hand;
536                 text-decoration: none;
537                 padding: 2px;
538 EOF
539
540         $opt->{anchor_down} = is_yes($opt->{anchor_down}) || 0;
541         my $top_timeout = $opt->{timeout} || 1000;
542
543         push @out, <<EOF;
544 <script language="JavaScript1.3">
545 var ${vpf}timeoutCode = -1;
546 var ${vpf}mydiv = '$fdiv';
547 var ${vpf}lines = new Array;
548 EOF
549
550         my %o = (
551                         start       => $opt->{tree_selector} || $opt->{name},
552                         file            => $opt->{file},
553                         table       => $opt->{table} || $::Variable->{MV_TREE_TABLE} || 'tree',
554                         master      => $opt->{tree_master} || 'parent_fld',
555                         subordinate => 'code',
556                         autodetect  => '1',
557                         no_open         => 1,
558                         js_prefix       => $vpf,
559                         sort        => $opt->{sort} || 'code',
560                         full        => '1',
561                         timed           => $opt->{timed},
562                         spacing     => '4',
563                         _transform   => $opt->{_transform},
564                 );
565
566         for(@{$opt->{_transform} || []}) {
567                 $o{$_} = $opt->{$_};
568         }
569
570         my $main;
571         my $rows;
572         if($opt->{iterator}) {
573                 $o{iterator} = $opt->{iterator};
574                 $main =  Vend::Tags->tree(\%o);
575                 $rows = $o{object}{mv_results};
576         }
577         else {
578                 $o{iterator} = \&transforms_only;
579                 Vend::Tags->tree(\%o);
580                 reset_transforms();
581                 delete $o{_transform};
582                 my @o;
583                 for(@{$o{object}{mv_results}}) {
584                         next if $_->{deleted};
585                         push @o, $_ unless $_->{deleted};
586                         $main .= tree_line(undef, $_, \%o);
587                 }
588                 $rows = \@o;
589         }
590
591         $rows->[-1]{mv_last_row} = 1 if @$rows;
592
593         # Prevent possibility of memory leak, reset last_line/first_line
594         reset_transforms();
595
596         push @out, $main;
597
598         my %seen;
599         my @levels = grep !$seen{$_}++, map { $_->{mv_level} } @$rows;
600         @levels = sort { $a <=> $b } @levels;
601         my $last = $#levels || 0;
602         shift @levels;
603
604         push @out, <<EOF;
605 var ${vpf}anchor_down = $opt->{anchor_down};
606 var ${vpf}link_prepend = '$opt->{link_prepend}';
607 var ${vpf}link_target = '$opt->{link_target}';
608 var ${vpf}last_level = $last;
609 var ${vpf}link_class = '$opt->{link_class}';
610 var ${vpf}link_class_open = '$opt->{link_class_open}';
611 var ${vpf}link_class_closed = '$opt->{link_class_closed}';
612 var ${vpf}link_style = '$opt->{link_style}';
613 var ${vpf}link_style_open = '$opt->{link_style_open}';
614 var ${vpf}link_style_closed = '$opt->{link_style_closed}';
615 var ${vpf}submenu_image_right = '$opt->{submenu_image_right}';
616 var ${vpf}submenu_image_left = '$opt->{submenu_image_left}';
617 EOF
618         push @out, <<EOF unless $opt->{no_emit_code};
619
620 // CLIP HERE
621 // If you want to move these functions to the HEAD
622         function ${vpf}menu_link (idx) {
623
624                 if( ${vpf}browserType() == "other" )
625                         return;
626
627                 var l = ${vpf}lines[ idx ];
628
629                 if(l == undefined) {
630                         alert("Bad idx=" + idx + ", no line there.");
631                         return;
632                 }
633
634                 var mouseo = '';
635
636                 var out = '<tr><td id="' + l[0] + 'left"';
637                 if(l[${vpf}MV_CHILDREN] > 0) {
638                         baseid = l[0];
639                         mouseo_beg = ' onMouseOver="${vpf}mousein(this,';
640                         mouseo_beg += l[${vpf}MV_LEVEL] + ',';
641                         mouseo_end = ')"';
642                         out += mouseo_beg + l[0] + mouseo_end;
643                 }
644
645                 out += '>';
646
647                 if(${vpf}submenu_image_left && l[${vpf}MV_CHILDREN] > 0) {
648                         if(${vpf}submenu_image_left.substr(0,1) == '<')
649                                 out += ${vpf}submenu_image_left;
650                         else
651                                 out += '<img src="' + ${vpf}submenu_image_left + '" border="0"$Vend::Xtrailer>';
652                 }
653                 out += '</td><td><div';
654                 
655                 if(l[${vpf}MV_CHILDREN] > 0) {
656                         out += ' id="' + l[0] + '"' + mouseo_beg + "''" + mouseo_end;
657                 }
658                 out += '>';
659                 var tstyle = ${vpf}link_style;
660                 var tclass = ${vpf}link_class;
661                 var ttarget = l[${vpf}TARGET];
662                 if(! ttarget)
663                         ttarget = ${vpf}link_target;
664                 var tprepend = ${vpf}link_prepend;
665                 if(l[${vpf}PAGE]) {
666                         out = out + '<a href="' + tprepend + l[ ${vpf}PAGE ] + '"';
667                         if(tclass)
668                                 out = out + ' class="' + tclass + '"';
669                         if(tstyle)
670                                 out = out + ' style="' + tstyle + '"';
671                         if(ttarget)
672                                 out = out + ' target="' + ttarget + '"';
673                         if(l[${vpf}DESCRIPTION])
674                                 out = out + ' title="' + l[ ${vpf}DESCRIPTION ] + '"';
675                         out = out + '>';
676                         out = out + l[ ${vpf}NAME ] + '</a>';
677                 }
678                 else {
679                         out = out + l[ ${vpf}NAME ];
680                 }
681         // alert("build idx=" + idx + " into: " + out);
682
683                 out += '</div></td><td id="' + l[0] + 'right"';
684
685                 if(l[${vpf}MV_CHILDREN] > 0) {
686                         out += mouseo_beg + l[0] + mouseo_end;
687                 }
688
689                 out += '>';
690                 if(${vpf}submenu_image_right && l[${vpf}MV_CHILDREN] > 0) {
691                         if(${vpf}submenu_image_right.substr(0,1) == '<')
692                                 out += ${vpf}submenu_image_right;
693                         else
694                                 out += '<img src="' + ${vpf}submenu_image_right + '" border="0"$Vend::Xtrailer>';
695                 }
696                 out += '</td></tr>';
697
698                 return out;
699         }
700
701         function ${vpf}mousein (obj,level,otherid) {
702                 if( ${vpf}browserType() == "other" )
703                         return;
704
705                 if(otherid != '' && otherid != undefined)
706                         obj = document.getElementById(otherid);
707
708                 if(level == undefined) 
709                         level = 0;
710                 level++;
711
712                 var divname = ${vpf}mydiv + level;
713
714                 var fod = document.getElementById( divname );
715                 if(fod == undefined) {
716                         return;
717                 }
718                 fod.style.display = 'none';
719                 clearTimeout( ${vpf}timeoutCode );
720                 ${vpf}timeoutCode = -1;
721
722                 var html = '<table cellpadding="0" cellspacing="0" border="0">';
723
724                 var idx = -1;
725                 var digid = obj.id;
726                 digid = digid.replace(/^$vpf/, '');
727                 for(var j = 0; j < ${vpf}lines.length; j++) {
728                         if(${vpf}lines[j][0] == digid) {
729                                 idx = j;
730                                 break;
731                         }
732                 }
733
734                 if(idx < 0) 
735                         return;
736         
737                 var l = ${vpf}lines[idx];
738                 var currentlevel = l[${vpf}MV_LEVEL];
739                 if(currentlevel == undefined)
740                         currentlevel = 0;
741
742                 ${vpf}menuClear(currentlevel);
743                 if(l[${vpf}MV_CHILDREN] < 1) 
744                         return;
745
746                 var x = ${vpf}getRightX( obj, currentlevel ) + 1;
747                 var y = ${vpf}getTopX( obj, currentlevel );
748                 var menu = fod.style;
749                 menu.left = x + "px";
750                 menu.top = y + "px";
751                 menu.display = 'block';
752
753                 var i;
754                 for( i = idx + 1; ; i++ )
755                 {
756                         var l = ${vpf}lines[i];
757 // alert("running link for level=" + l[${vpf}MV_LEVEL] + ", line=" + l);
758                         if(l == undefined || l[${vpf}MV_LEVEL] < level)
759                                 break;
760                         if(l[${vpf}MV_LEVEL] == level)
761                                 html += ${vpf}menu_link(i);
762                 }
763                 html += '</table>';
764                 fod.innerHTML = html;
765         }
766
767         function ${vpf}getRightX( obj, level )
768         {
769                 if( ${vpf}browserType() == "other" )
770                         return;
771                 var pos = 0;
772                 var n = 0;
773                 var x = obj.offsetParent;
774                 if(x == undefined) 
775                         x = obj;
776                 while(x.offsetParent != undefined) {
777                         n += x.offsetLeft;
778                         x = x.offsetParent;
779                 }
780                 pos = n + obj.offsetLeft;
781                 if(${vpf}anchor_down != 1 || level > 0)
782                         pos += obj.offsetWidth;
783                 return pos;
784         }
785
786         function ${vpf}getTopX( obj, level )
787         {
788                 if( ${vpf}browserType() == "other" )
789                         return;
790
791                 var pos = 0;
792                 var n = 0;
793                 var x = obj;
794                 while(x.offsetParent != undefined) {
795                         n += x.offsetParent.offsetTop;
796                         x = x.offsetParent;
797                 }
798                 pos = n + obj.offsetTop;
799                 if(${vpf}anchor_down && level == 0)
800                         pos += obj.offsetHeight;
801                 return pos;
802         }
803         
804         function ${vpf}mouseout( obj, level )
805         {
806                 if( ${vpf}browserType() == "other" )
807                         return;
808
809                 if(level == undefined) 
810                         level = 0;
811                 level++;
812                 ${vpf}timeoutCode = setTimeout( "${vpf}menuClear();", $top_timeout );
813         }
814
815         function ${vpf}menuClear(level)
816         {
817                 if( ${vpf}browserType() == "other" )
818                         return;
819
820                 if (level == undefined)
821                         level = 0;
822                 level++;
823                 for( var i = level; i <= ${vpf}last_level; i++) {
824                         var thisdiv = ${vpf}mydiv + i;
825                         var fod = document.getElementById( thisdiv );
826                         if(fod != undefined)
827                                 fod.style.display = 'none';
828                 }
829                 clearTimeout( ${vpf}timeoutCode );
830                 ${vpf}timeoutCode = -1;
831         }
832
833         function ${vpf}menuBusy()
834         {
835                 if( ${vpf}browserType() == "other" )
836                         return;
837
838                 clearTimeout( ${vpf}timeoutCode );
839                 ${vpf}timeoutCode = -1;
840         }
841
842         var ${vpf}clientType = "unknown";
843
844         function ${vpf}browserType()
845         {
846                 if( ${vpf}clientType != "unknown"  )
847                         return ${vpf}clientType;
848         
849                 ${vpf}clientType = "other";
850                 if (document.all) {
851                         if( document.getElementById )
852                                 ${vpf}clientType = "ie";
853                 }
854                 else if (document.layers) {
855                 }
856                 else if (document.getElementById) {
857                         ${vpf}clientType = "ns6";
858                 }
859                 else
860                 {
861                 }
862
863                 return ${vpf}clientType;
864         }
865
866 // END CLIP
867 EOF
868
869         push @out, <<EOF;
870 </script>
871 EOF
872
873         for(@levels) {
874                 push @out, <<EOF;
875 <div class="$opt->{flyout_class}" id="$fdiv$_" style="
876                                                 position:absolute;
877                                                 display:none;
878                                                 $opt->{flyout_style}
879                                         "
880                  OnMouseOver="${vpf}menuBusy();" OnMouseOut="${vpf}mouseout();"></div>
881 EOF
882         }
883
884         my $header;
885         $header = ::interpolate_html($opt->{header_template})
886                 if $opt->{header_template};
887         if($header =~ /\S/) {
888                 $header = Vend::Tags->uc_attr_list($opt, $header);
889                 push @out, $header;
890         }
891
892         for my $row (@$rows) {
893                 next if $row->{deleted};
894                 extra_value($opt->{extra_value}, $row)
895                         if $opt->{extra_value};
896                 push @out, Vend::Tags->uc_attr_list($row, $template);
897         }
898
899         my $footer;
900         $footer = ::interpolate_html($opt->{footer_template})
901                 if $opt->{footer_template};
902         if($footer =~ /\S/) {
903                 $footer = Vend::Tags->uc_attr_list($opt, $footer);
904                 push @out, $footer;
905         }
906
907         return join "", @out;
908 }
909
910 sub file_tree {
911         my($name, $opt, $template) = @_;
912         my @out;
913         # out 0
914
915         my $vpf = $opt->{js_prefix} ||= 'mv_';
916         $opt->{toggle_class} ||= '';
917         $opt->{explode_url} ||= "javascript:${vpf}do_explode(); void(0)";
918         $opt->{collapse_url} ||= "javascript:${vpf}do_collapse(); void(0)";
919         my $explode_label = errmsg($opt->{explode_label} || 'Explode tree');
920         my $collapse_label = errmsg($opt->{collapse_label} || 'Collapse tree');
921         $opt->{header_template} ||= <<EOF;
922 <p>
923 <a href="{EXPLODE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$explode_label</a><br$Vend::Xtrailer>
924 <a href="{COLLAPSE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$collapse_label</a>
925 </p>
926 EOF
927
928         my $header;
929         $header = ::interpolate_html($opt->{header_template})
930                 if $opt->{header_template};
931         if($header =~ /\S/) {
932                 $header = Vend::Tags->uc_attr_list($opt, $header);
933                 push @out, $header;
934         }
935
936         $opt->{div_style} ||= '';
937         push @out, <<EOF;
938
939 <div id="${vpf}treebox" style="visibility: Visible">
940 </div>
941 <script language="JavaScript1.3">
942 var ${vpf}lines = new Array;
943 var ${vpf}sary = new Array;
944 EOF
945
946         my %o = (
947                         start       => $opt->{tree_selector} || 'Products',
948                         table       => $opt->{table} || $::Variable->{MV_TREE_TABLE} || 'tree',
949                         master      => $opt->{tree_master} || 'parent_fld',
950                         file            => $opt->{file},
951                         subordinate => 'code',
952                         autodetect  => '1',
953                         open_variable => $opt->{open_variable} || 'open',
954                         sort        => $opt->{sort} || 'code',
955                         js_prefix       => $vpf,
956                         full        => '1',
957                         timed           => $opt->{timed},
958                         spacing     => '4',
959                         _transform   => $opt->{_transform},
960                 );
961         
962         for(@{$opt->{_transform} || []}) {
963                 $o{$_} = $opt->{$_};
964         }
965
966         my $main;
967         my $rows;
968         if($opt->{iterator}) {
969                 $o{iterator} = $opt->{iterator};
970                 $main =  Vend::Tags->tree(\%o);
971                 $rows = $o{object}{mv_results};
972         }
973         else {
974                 $o{iterator} = \&transforms_only;
975                 Vend::Tags->tree(\%o);
976                 reset_transforms();
977                 delete $o{_transform};
978                 my @o;
979                 for(@{$o{object}{mv_results}}) {
980                         next if $_->{deleted};
981                         push @o, $_ unless $_->{deleted};
982                         $main .= tree_line(undef, $_, \%o);
983                 }
984                 $rows = \@o;
985         }
986
987         $rows->[-1]{mv_last_row} = 1 if @$rows;
988
989         my $openvar = $opt->{open_variable} || 'open';
990
991         push @out, $main;
992         if(defined $CGI::values{$openvar}) {
993                  $::Scratch->{dhtml_tree_open} = $CGI::values{$openvar};
994         }
995         else {
996                 $CGI::values{$openvar} = $::Scratch->{dhtml_tree_open};
997         }
998         my $out = "  var ${vpf}openstatus = [";
999         my @open =  split /,/, $CGI::values{$openvar};
1000         my @o;
1001
1002         my %hsh = (map { ($_, 1) } @open);
1003
1004         for(0 .. $open[$#open]) {
1005                 push @o, ($hsh{$_} ? 1 : 0);
1006         }
1007         $out .= join ",", @o;
1008         $out .= "];\n";
1009         $out .= " var ${vpf}explode = ";
1010         $out .= $CGI::values{$opt->{explode_variable} || 'explode'} ? 1 : 0;
1011         $out .= ";\n";
1012         $out .= " var ${vpf}collapse = ";
1013         $out .= $CGI::values{$opt->{collapse_variable} || 'collapse'} ? 1 : 0;
1014         $out .= ";\n";
1015
1016         push @out, $out;
1017
1018         my $Tag = new Vend::Tags;
1019
1020         if($opt->{specific_image_toggle}) {
1021                 $opt->{specific_image_toggle} =~ s/\D+//;
1022                 if(defined $opt->{specific_image_base}) {
1023                         $opt->{specific_image_base} =~ s:/*$:/:;
1024                 }
1025                 else {
1026                         $opt->{specific_image_base} = $Vend::Cfg->{ImageDir};
1027                 }
1028         }
1029
1030         if($opt->{specific_image_link}) {
1031                 if(defined $opt->{specific_image_base}) {
1032                         $opt->{specific_image_base} =~ s:/*$:/:;
1033                 }
1034                 else {
1035                         $opt->{specific_image_base} = $Vend::Cfg->{ImageDir};
1036                 }
1037         }
1038
1039         $opt->{image_link_extra} = $Tag->jsq($opt->{image_link_extra});
1040         $opt->{image_link_extra} ||= qq{'border="0"'};
1041
1042         $opt->{specific_image_toggle} ||= 0;
1043
1044         $opt->{img_node} ||= 'node.gif';
1045         $opt->{img_lastnode} ||= 'lastnode.gif';
1046         $opt->{img_spacenode} ||= 'vertline.gif';
1047         my $node_extra = $opt->{img_clear_extra};
1048         $node_extra =~ s/\bheight\s*=\s*"?\d+"?\s*//;
1049
1050         for(qw/img_node img_lastnode img_spacenode/) {
1051                         $opt->{$_} = $Tag->image({
1052                                                                         src => $opt->{$_},
1053                                                                         border => 0,
1054                                                                         extra => 'align=absbottom',
1055                                                                 });
1056 #::logDebug("$_=$opt->{$_}");
1057         }
1058
1059         my $canonwidth;
1060         $opt->{img_node} =~ m{\bwidth\s*=\s*"?(\d+)"?} 
1061                 and $canonwidth = $1;
1062
1063         my $canonheight;
1064         $opt->{img_node} =~ m{\bheight\s*=\s*"?(\d+)"?} 
1065                 and $canonheight = $1;
1066
1067         if($canonwidth) {
1068                 $opt->{toggle_anchor_clear} =~ s{\bwidth\s*=\s*"*\d+"*}{width="$canonwidth"};
1069         }
1070         if($canonheight) {
1071                 $opt->{toggle_anchor_clear} =~ s{\bheight\s*=\s*"*\d+"*}{height="$canonheight"};
1072         }
1073
1074         $opt->{toggle_anchor_clear} =~ s{\balign\s*=\s*"*\w+"*}{}i;
1075         $opt->{toggle_anchor_clear} =~ s{\s*>}{ align="absbottom">}i;
1076
1077 #::logDebug("toggle_anchor_clear=$opt->{toggle_anchor_clear}");
1078
1079         my $width = $1;
1080         my $ihash;
1081         $opt->{icon_by_type} ||= qq{
1082                 pdf=pdf.gif
1083                 html=html.gif
1084                 htm=html.gif
1085                 xls=xls.gif
1086                 ppt=ppt.gif
1087                 doc=doc.gif
1088         };
1089
1090         if($opt->{icon_by_type} and $ihash = get_option_hash($opt->{icon_by_type}) ) {
1091                 push @out, <<EOF;
1092 var ${vpf}icon_by_type = 1;
1093 var ${vpf}icon = new Array;
1094 var ${vpf}img_node = '$opt->{img_node}';
1095 var ${vpf}img_lastnode = '$opt->{img_lastnode}';
1096 var ${vpf}img_spacenode = '$opt->{img_spacenode}';
1097 if(! ${vpf}img_lastnode)
1098         ${vpf}img_lastnode = ${vpf}img_node;
1099 EOF
1100                 for(keys %$ihash) {
1101                         my $img = $Tag->image({
1102                                                                 src => $ihash->{$_},
1103                                                                 src_only => 1,
1104                                                         });
1105                         push @out, qq{${vpf}icon['$_'] = '$img';\n};
1106                 }
1107         }
1108         else {
1109                 push @out, <<EOF;
1110 var ${vpf}icon_by_type = 0;
1111 EOF
1112         }
1113
1114         $opt->{no_open} = $opt->{no_open} ? 1 : 0;
1115
1116         push @out, <<EOF;
1117 var ${vpf}next_level = 0;
1118 var ${vpf}no_open = $opt->{no_open};
1119 var ${vpf}no_wrap = '$opt->{no_wrap}';
1120 var ${vpf}openstring = '';
1121 var ${vpf}link_prepend = '$opt->{link_prepend}';
1122 var ${vpf}link_target = '$opt->{link_target}';
1123 var ${vpf}link_class = '$opt->{link_class}';
1124 var ${vpf}link_class_open = '$opt->{link_class_open}';
1125 var ${vpf}link_class_closed = '$opt->{link_class_closed}';
1126 var ${vpf}link_style = '$opt->{link_style}';
1127 var ${vpf}link_style_open = '$opt->{link_style_open}';
1128 var ${vpf}link_style_closed = '$opt->{link_style_closed}';
1129 var ${vpf}specific_image_toggle = $opt->{specific_image_toggle};
1130 var ${vpf}specific_image_base = '$opt->{specific_image_base}';
1131 var ${vpf}specific_image_link;
1132 var ${vpf}image_link_extra = $opt->{image_link_extra};
1133 var ${vpf}toggle_class = '$opt->{toggle_class}';
1134 var ${vpf}toggle_anchor_clear = '$opt->{toggle_anchor_clear}';
1135 var ${vpf}toggle_anchor_closed = '$opt->{toggle_anchor_closed}';
1136 var ${vpf}toggle_anchor_open = '$opt->{toggle_anchor_open}';
1137 var ${vpf}treebox = document.getElementById('${vpf}treebox');
1138 if(${vpf}image_link_extra)
1139         ${vpf}image_link_extra = ' ' + ${vpf}image_link_extra;
1140 var alert_shown;
1141 EOF
1142
1143         push @out, "${vpf}specific_image_link = 1;"
1144                 if $opt->{specific_image_link};
1145
1146         push @out, <<EOF unless $opt->{no_emit_code};
1147
1148 function ${vpf}image_link (rec) {
1149         if(rec == undefined)
1150                 return;
1151         var out;
1152         if(rec[ ${vpf}IMG_UP ]) {
1153                 out = '<img src="';
1154                 out += ${vpf}specific_image_base;
1155                 out += rec[ ${vpf}IMG_UP ];
1156                 out += '"';
1157                 out += ${vpf}image_link_extra;
1158                 out += '$Vend::Xtrailer>';
1159 // alert('img=' + out);
1160         }
1161         else {
1162                 out = rec[${vpf}NAME];
1163         }
1164         return out;
1165 }
1166
1167 var donewarn = 0;
1168
1169 function ${vpf}tree_link (idx) {
1170
1171         var out = '';
1172
1173         if(${vpf}no_wrap) {
1174                 out += '<div style="white-space: nowrap; margin: 0; padding: 0">';
1175         }
1176
1177         var l = ${vpf}lines[idx];
1178         var nxt_l = ${vpf}lines[idx + 1];
1179         if(! nxt_l) 
1180                 nxt_l = new Array;
1181
1182         if(l == undefined) {
1183                 alert("Bad idx=" + idx + ", no line there.");
1184                 return;
1185         }
1186
1187         if(l[${vpf}MV_LEVEL] > ${vpf}next_level)
1188                 return '';
1189
1190         var spec_toggle = 0;
1191         if(${vpf}specific_image_toggle > 0) {
1192                 var toglevel = ${vpf}specific_image_toggle - 1;
1193 // if(alert_shown == undefined) {
1194 // alert('specific image toggle triggered, toglevel=' + toglevel + ", mv_level=" + l[${vpf}MV_LEVEL]);
1195 // alert_shown = 1;
1196 // }
1197                 if(l[${vpf}MV_LEVEL] <= toglevel) {
1198                         spec_toggle = 1;
1199                 }
1200         }
1201
1202         var needed = l[${vpf}MV_LEVEL];
1203         var spacer = '';
1204         var nodeimg = '';
1205         if(l[${vpf}MV_LEVEL] && ${vpf}icon_by_type) {
1206                 var k;
1207                 for(k = idx + 1; ${vpf}lines[k] && ${vpf}lines[k][${vpf}MV_LEVEL] > l[${vpf}MV_LEVEL]; k++) {
1208                         // do nothing
1209                 }
1210                 if( ! ${vpf}lines[k] || ${vpf}lines[k][${vpf}MV_LEVEL] < l[${vpf}MV_LEVEL] ) {
1211                         nodeimg = ${vpf}img_lastnode;
1212                         ${vpf}sary[needed] = ${vpf}toggle_anchor_clear;
1213                 }
1214                 else {
1215                         nodeimg = ${vpf}img_node;
1216                         ${vpf}sary[needed] = ${vpf}img_spacenode;
1217                 }
1218         }
1219         else {
1220                 ${vpf}sary[needed] = '&nbsp;&nbsp;&nbsp;&nbsp;';
1221         }
1222
1223         var i;
1224
1225         if(${vpf}icon_by_type && needed)  {
1226                 needed -= 1;
1227         }
1228
1229         for(i = 1; i <= needed; i++)
1230                 out += ${vpf}sary[i];
1231
1232         var tstyle = ${vpf}link_style;
1233         var tclass = ${vpf}link_class;
1234         var ttarget = l[${vpf}TARGET];
1235         if(! ttarget)
1236                 ttarget = ${vpf}link_target;
1237         var tprepend = ${vpf}link_prepend;
1238         if(l[${vpf}MV_CHILDREN] > 0) {
1239                 if(l[${vpf}MV_LEVEL] && ${vpf}icon_by_type) {
1240                         var k;
1241                         for(k = idx; ${vpf}lines[k][${vpf}MV_LEVEL] > l[${vpf}MV_LEVEL]; k++) {
1242                                 // do nothing
1243                         }
1244                         out += nodeimg;
1245                 }
1246                 if(${vpf}openstatus[idx] == 1) {
1247                         tclass = ${vpf}link_class_open;
1248                         tstyle = ${vpf}link_style_open;
1249                         if(spec_toggle > 0) {
1250                                 tanchor = '<img border="0" align="absbottom"  src="' + ${vpf}specific_image_base + l[${vpf}IMG_DN] + '"$Vend::Xtrailer>';
1251                         }
1252                         else {
1253                                 tanchor = ${vpf}toggle_anchor_open;
1254                         }
1255                         ${vpf}next_level = l[${vpf}MV_LEVEL] + 1;
1256                 }
1257                 else {
1258                         tclass = ${vpf}link_class_closed;
1259                         tstyle = ${vpf}link_style_closed;
1260                         if(spec_toggle > 0) {
1261                                 tanchor = '<img border="0" align="absbottom"  src="' + ${vpf}specific_image_base + l[${vpf}IMG_UP] + '"$Vend::Xtrailer>';
1262 // if(alert_shown < 2) {
1263 // alert('tanchor=' + tanchor);
1264 // alert_shown = 2;
1265 // }
1266                         }
1267                         else {
1268                                 tanchor = ${vpf}toggle_anchor_closed;
1269                         }
1270                         ${vpf}next_level = l[${vpf}MV_LEVEL];
1271                 }
1272
1273                 out = out + '<a href="javascript:${vpf}toggit(' + idx + ');void(0)"';
1274                 if(tclass)
1275                         out = out + ' class="' + tclass + '"';
1276                 if(tstyle)
1277                         out = out + ' style="' + tstyle + '"';
1278                 out = out + '>';
1279                 out = out + tanchor;
1280                 out = out + '</a>';
1281         }
1282         else {
1283                 if(! ${vpf}icon_by_type)
1284                         out = out + ${vpf}toggle_anchor_clear;
1285                 next_level = l[${vpf}MV_LEVEL];
1286         }
1287
1288         if(spec_toggle == 0) {
1289                 if(l[${vpf}PAGE]) {
1290                         out = out + '<a href="' + tprepend + l[${vpf}PAGE];
1291
1292                         if(! ${vpf}no_open) 
1293                                 out += ${vpf}openstring;
1294
1295                         out += '"';
1296
1297                         if(tclass)
1298                                 out = out + ' class="' + tclass + '"';
1299                         if(tstyle)
1300                                 out = out + ' style="' + tstyle + '"';
1301                         if(ttarget)
1302                                 out = out + ' target="' + ttarget + '"';
1303                         if(l[${vpf}DESCRIPTION])
1304                                 out = out + ' title="' + l[${vpf}DESCRIPTION] + '"';
1305                         out = out + '>';
1306                         if(${vpf}icon_by_type) {
1307                                 var fn = l[ ${vpf}PAGE ];
1308                                 var fpos = fn.lastIndexOf('.');
1309                                 var ext = fn.substr(fpos + 1);
1310
1311                                 if(${vpf}img_node) {
1312                                         out += nodeimg;
1313                                 }
1314
1315                                 if(${vpf}icon[ ext ]) {
1316                                         out += '<img border="0" align="absbottom" src="';
1317                                         out += ${vpf}icon[ ext ];
1318                                         out += '"$Vend::Xtrailer>';
1319                                 }
1320                         }
1321                         if(${vpf}specific_image_link) 
1322                                 out += ${vpf}image_link(l);
1323                         else
1324                                 out += l[${vpf}NAME];
1325                         out += '</a>';
1326                 }
1327                 else {
1328                         if(tstyle || tclass) {
1329                                 out += "<span";
1330                                 if(tclass) 
1331                                         out += ' class="' + tclass + '"';
1332                                 if(tstyle) 
1333                                         out += ' style="' + tstyle + '"';
1334                                 out += ">" + l[${vpf}NAME] + '</span>';
1335                         }
1336                         else {
1337                                 out = out + l[${vpf}NAME];
1338                         }
1339                 }
1340         }
1341
1342         if(${vpf}no_wrap) {
1343                 out += '</div>';
1344         }
1345         else {
1346                 out += "<br$Vend::Xtrailer>";
1347         }
1348
1349         return out;
1350 }
1351
1352 function ${vpf}toggit (idx) {
1353
1354         var l = ${vpf}lines[idx];
1355         if(l == undefined) {
1356                 alert("bad index " + idx);
1357                 return;
1358         }
1359         if(l[${vpf}MV_CHILDREN] < 1) {
1360                 alert("nothing to toggle at index " + idx);
1361                 return;
1362         }
1363
1364         ${vpf}openstatus[idx] = ${vpf}openstatus[idx] == 1 ? 0 : 1;
1365         ${vpf}gen_openstring();
1366         ${vpf}rewrite_tree();
1367 }
1368 function ${vpf}gen_openstring () {
1369         ${vpf}openstring = '';
1370
1371         for(var p = 0; p < ${vpf}openstatus.length; p++) {
1372                 if(${vpf}openstatus[p])
1373                         ${vpf}openstring += p + ',';
1374         }
1375         ${vpf}openstring = ${vpf}openstring.replace(/,+\$/, '');
1376         return;
1377 }
1378 function ${vpf}do_explode () {
1379         for(var i = 0; i < ${vpf}lines.length; i++)
1380                 ${vpf}openstatus[i] = 1;
1381         ${vpf}gen_openstring();
1382         ${vpf}rewrite_tree();
1383 }
1384 function ${vpf}do_collapse () {
1385         for(var i = 0; i < ${vpf}lines.length; i++)
1386                 ${vpf}openstatus[i] = 0;
1387         ${vpf}gen_openstring();
1388         ${vpf}rewrite_tree();
1389 }
1390 function ${vpf}rewrite_tree () {
1391         var thing = '';
1392         for(i = 0; i < ${vpf}lines.length; i++) {
1393                 thing = thing + ${vpf}tree_link(i);
1394         }
1395         ${vpf}treebox.innerHTML = thing;
1396         ${vpf}next_level = 0;
1397 }
1398
1399 // END CLIP
1400
1401 EOF
1402
1403         push @out, <<EOF;
1404
1405 if(${vpf}collapse == 1 || ${vpf}explode == 1) {
1406         ${vpf}openstatus.length = 0;
1407 }
1408 for( var i = 0; i < ${vpf}lines.length; i++) {
1409         if(${vpf}openstatus[i] == undefined)
1410                 ${vpf}openstatus[i] = ${vpf}explode;
1411 }
1412
1413 ${vpf}collapse = 0;
1414 ${vpf}explode = 0;
1415 ${vpf}gen_openstring();
1416 ${vpf}rewrite_tree();
1417 </script>
1418 EOF
1419
1420         my $footer;
1421         $footer = ::interpolate_html($opt->{footer_template})
1422                 if $opt->{footer_template};
1423         if($footer =~ /\S/) {
1424                 $footer = Vend::Tags->uc_attr_list($opt, $footer);
1425                 push @out, $footer;
1426         }
1427
1428         return join "\n", @out;
1429 }
1430
1431 sub dhtml_tree {
1432         my($name, $opt, $template) = @_;
1433         my @out;
1434         # out 0
1435
1436         my $vpf = $opt->{js_prefix} ||= 'mv_';
1437         $opt->{toggle_class} ||= '';
1438         $opt->{explode_url} ||= "javascript:${vpf}do_explode(); void(0)";
1439         $opt->{collapse_url} ||= "javascript:${vpf}do_collapse(); void(0)";
1440         my $explode_label = errmsg($opt->{explode_label} || 'Explode tree');
1441         my $collapse_label = errmsg($opt->{collapse_label} || 'Collapse tree');
1442         $opt->{header_template} ||= <<EOF;
1443 <p>
1444 <a href="{EXPLODE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$explode_label</a><br$Vend::Xtrailer>
1445 <a href="{COLLAPSE_URL}" {LINK_STYLE?} style="{LINK_STYLE}"{/LINK_STYLE?} {LINK_CLASS?} class="{LINK_CLASS}"{/LINK_CLASS?}>$collapse_label</a>
1446 </p>
1447 EOF
1448
1449         my $header;
1450         $header = ::interpolate_html($opt->{header_template})
1451                 if $opt->{header_template};
1452         if($header =~ /\S/) {
1453                 $header = Vend::Tags->uc_attr_list($opt, $header);
1454                 push @out, $header;
1455         }
1456
1457         $opt->{div_style} ||= '';
1458         push @out, <<EOF;
1459
1460 <div id="${vpf}treebox" style="visibility: Visible">
1461 </div>
1462 <script language="JavaScript1.3">
1463 var ${vpf}lines = new Array;
1464 EOF
1465
1466         my %o = (
1467                         start       => $opt->{tree_selector} || 'Products',
1468                         table       => $opt->{table} || $::Variable->{MV_TREE_TABLE} || 'tree',
1469                         master      => $opt->{tree_master} || 'parent_fld',
1470                         file            => $opt->{file},
1471                         subordinate => 'code',
1472                         autodetect  => '1',
1473                         open_variable => $opt->{open_variable} || 'open',
1474                         sort        => $opt->{sort} || 'code',
1475                         js_prefix       => $vpf,
1476                         full        => '1',
1477                         timed           => $opt->{timed},
1478                         spacing     => '4',
1479                         _transform   => $opt->{_transform},
1480                 );
1481         
1482         for(@{$opt->{_transform} || []}) {
1483                 $o{$_} = $opt->{$_};
1484         }
1485
1486         my $main;
1487         my $rows;
1488         if($opt->{iterator}) {
1489                 $o{iterator} = $opt->{iterator};
1490                 $main =  Vend::Tags->tree(\%o);
1491                 $rows = $o{object}{mv_results};
1492         }
1493         else {
1494                 $o{iterator} = \&transforms_only;
1495                 Vend::Tags->tree(\%o);
1496                 reset_transforms();
1497                 delete $o{_transform};
1498                 my @o;
1499                 for(@{$o{object}{mv_results}}) {
1500                         next if $_->{deleted};
1501                         push @o, $_ unless $_->{deleted};
1502                         $main .= tree_line(undef, $_, \%o);
1503                 }
1504                 $rows = \@o;
1505         }
1506
1507         $rows->[-1]{mv_last_row} = 1 if @$rows;
1508
1509         my $openvar = $opt->{open_variable} || 'open';
1510
1511         push @out, $main;
1512         if(defined $CGI::values{$openvar}) {
1513                  $::Scratch->{dhtml_tree_open} = $CGI::values{$openvar};
1514         }
1515         else {
1516                 $CGI::values{$openvar} = $::Scratch->{dhtml_tree_open};
1517         }
1518         my $out = "  var ${vpf}openstatus = [";
1519         my @open =  split /,/, $CGI::values{$openvar};
1520         my @o;
1521
1522         my %hsh = (map { ($_, 1) } @open);
1523
1524         for(0 .. $open[$#open]) {
1525                 push @o, ($hsh{$_} ? 1 : 0);
1526         }
1527         $out .= join ",", @o;
1528         $out .= "];\n";
1529         $out .= " var ${vpf}explode = ";
1530         $out .= $CGI::values{$opt->{explode_variable} || 'explode'} ? 1 : 0;
1531         $out .= ";\n";
1532         $out .= " var ${vpf}collapse = ";
1533         $out .= $CGI::values{$opt->{collapse_variable} || 'collapse'} ? 1 : 0;
1534         $out .= ";\n";
1535
1536         push @out, $out;
1537
1538         if($opt->{specific_image_toggle}) {
1539                 $opt->{specific_image_toggle} =~ s/\D+//;
1540                 if(defined $opt->{specific_image_base}) {
1541                         $opt->{specific_image_base} =~ s:/*$:/:;
1542                 }
1543                 else {
1544                         $opt->{specific_image_base} = $Vend::Cfg->{ImageDir};
1545                 }
1546         }
1547
1548         if($opt->{specific_image_link}) {
1549                 if(defined $opt->{specific_image_base}) {
1550                         $opt->{specific_image_base} =~ s:/*$:/:;
1551                 }
1552                 else {
1553                         $opt->{specific_image_base} = $Vend::Cfg->{ImageDir};
1554                 }
1555         }
1556
1557         $opt->{image_link_extra} = Vend::Tags->jsq($opt->{image_link_extra});
1558         $opt->{image_link_extra} ||= qq{'border="0"'};
1559
1560         $opt->{specific_image_toggle} ||= 0;
1561
1562         push @out, <<EOF;
1563 var ${vpf}next_level = 0;
1564 var ${vpf}openstring = '';
1565 var ${vpf}link_class = '$opt->{link_class}';
1566 var ${vpf}link_class_open = '$opt->{link_class_open}';
1567 var ${vpf}link_class_closed = '$opt->{link_class_closed}';
1568 var ${vpf}link_style = '$opt->{link_style}';
1569 var ${vpf}link_style_open = '$opt->{link_style_open}';
1570 var ${vpf}link_style_closed = '$opt->{link_style_closed}';
1571 var ${vpf}specific_image_toggle = $opt->{specific_image_toggle};
1572 var ${vpf}specific_image_base = '$opt->{specific_image_base}';
1573 var ${vpf}specific_image_link;
1574 var ${vpf}image_link_extra = $opt->{image_link_extra};
1575 var ${vpf}toggle_class = '$opt->{toggle_class}';
1576 var ${vpf}toggle_anchor_clear = '$opt->{toggle_anchor_clear}';
1577 var ${vpf}toggle_anchor_closed = '$opt->{toggle_anchor_closed}';
1578 var ${vpf}toggle_anchor_open = '$opt->{toggle_anchor_open}';
1579 var ${vpf}treebox = document.getElementById('${vpf}treebox');
1580 if(${vpf}image_link_extra)
1581         ${vpf}image_link_extra = ' ' + ${vpf}image_link_extra;
1582 var alert_shown;
1583 EOF
1584
1585         push @out, "${vpf}specific_image_link = 1;"
1586                 if $opt->{specific_image_link};
1587
1588         push @out, <<EOF unless $opt->{no_emit_code};
1589
1590 function ${vpf}image_link (rec) {
1591         if(rec == undefined)
1592                 return;
1593         var out;
1594         if(rec[ ${vpf}IMG_UP ]) {
1595                 out = '<img src="';
1596                 out += ${vpf}specific_image_base;
1597                 out += rec[ ${vpf}IMG_UP ];
1598                 out += '"';
1599                 out += ${vpf}image_link_extra;
1600                 out += '$Vend::Xtrailer>';
1601 // alert('img=' + out);
1602         }
1603         else {
1604                 out = rec[${vpf}NAME];
1605         }
1606         return out;
1607 }
1608
1609 function ${vpf}tree_link (idx) {
1610
1611         var out = '';
1612
1613         var l = ${vpf}lines[idx];
1614
1615         if(l == undefined) {
1616                 alert("Bad idx=" + idx + ", no line there.");
1617                 return;
1618         }
1619
1620         if(l[${vpf}MV_LEVEL] > ${vpf}next_level)
1621                 return '';
1622
1623         var spec_toggle = 0;
1624         if(${vpf}specific_image_toggle > 0) {
1625                 var toglevel = ${vpf}specific_image_toggle - 1;
1626 // if(alert_shown == undefined) {
1627 // alert('specific image toggle triggered, toglevel=' + toglevel + ", mv_level=" + l[${vpf}MV_LEVEL]);
1628 // alert_shown = 1;
1629 // }
1630                 if(l[${vpf}MV_LEVEL] <= toglevel) {
1631                         spec_toggle = 1;
1632                 }
1633         }
1634
1635         var i;
1636         var needed = l[${vpf}MV_LEVEL];
1637         for(i = 1; i <= needed; i++)
1638                 out = out + '&nbsp;&nbsp;&nbsp;&nbsp;';
1639
1640         var tstyle = ${vpf}link_style;
1641         var tclass = ${vpf}link_class;
1642         if(l[${vpf}MV_CHILDREN] > 0) {
1643                 if(${vpf}openstatus[idx] == 1) {
1644                         tclass = ${vpf}link_class_open;
1645                         tstyle = ${vpf}link_style_open;
1646                         if(spec_toggle > 0) {
1647                                 tanchor = '<img border="0" src="' + ${vpf}specific_image_base + l[${vpf}IMG_DN] + '"$Vend::Xtrailer>';
1648 // if(alert_shown < 2) {
1649 // alert('tanchor=' + tanchor);
1650 // alert_shown = 2;
1651 // }
1652                         }
1653                         else {
1654                                 tanchor = ${vpf}toggle_anchor_open;
1655                         }
1656                         ${vpf}next_level = l[${vpf}MV_LEVEL] + 1;
1657                 }
1658                 else {
1659                         tclass = ${vpf}link_class_closed;
1660                         tstyle = ${vpf}link_style_closed;
1661                         if(spec_toggle > 0) {
1662                                 tanchor = '<img border="0" src="' + ${vpf}specific_image_base + l[${vpf}IMG_UP] + '"$Vend::Xtrailer>';
1663 // if(alert_shown < 2) {
1664 // alert('tanchor=' + tanchor);
1665 // alert_shown = 2;
1666 // }
1667                         }
1668                         else {
1669                                 tanchor = ${vpf}toggle_anchor_closed;
1670                         }
1671                         ${vpf}next_level = l[${vpf}MV_LEVEL];
1672                 }
1673
1674                 out = out + '<a href="javascript:${vpf}toggit(' + idx + ');void(0)"';
1675                 if(tclass)
1676                         out = out + ' class="' + tclass + '"';
1677                 if(tstyle)
1678                         out = out + ' style="' + tstyle + '"';
1679                 out = out + '>';
1680                 out = out + tanchor;
1681                 out = out + '</a>';
1682         }
1683         else {
1684                 out = out + ${vpf}toggle_anchor_clear;
1685                 next_level = l[${vpf}MV_LEVEL];
1686         }
1687
1688         if(spec_toggle == 0) {
1689                 if(l[${vpf}PAGE]) {
1690                         out = out + '<a href="' + l[${vpf}PAGE] + ${vpf}openstring + '"';
1691                         if(tclass)
1692                                 out = out + ' class="' + tclass + '"';
1693                         if(tstyle)
1694                                 out = out + ' style="' + tstyle + '"';
1695                         if(l[${vpf}DESCRIPTION])
1696                                 out = out + ' title="' + l[${vpf}DESCRIPTION] + '"';
1697                         out = out + '>';
1698                         if(${vpf}specific_image_link) 
1699                                 out += ${vpf}image_link(l);
1700                         else
1701                                 out += l[${vpf}NAME];
1702                         out += '</a>';
1703                 }
1704                 else {
1705                         out = out + l[${vpf}NAME];
1706                 }
1707         }
1708         out = out + "<br$Vend::Xtrailer>";
1709
1710         return out;
1711 }
1712
1713 function ${vpf}toggit (idx) {
1714
1715         var l = ${vpf}lines[idx];
1716         if(l == undefined) {
1717                 alert("bad index " + idx);
1718                 return;
1719         }
1720         if(l[${vpf}MV_CHILDREN] < 1) {
1721                 alert("nothing to toggle at index " + idx);
1722                 return;
1723         }
1724
1725         ${vpf}openstatus[idx] = ${vpf}openstatus[idx] == 1 ? 0 : 1;
1726         ${vpf}gen_openstring();
1727         ${vpf}rewrite_tree();
1728 }
1729 function ${vpf}gen_openstring () {
1730         ${vpf}openstring = '';
1731         for(var p = 0; p < ${vpf}openstatus.length; p++) {
1732                 if(${vpf}openstatus[p])
1733                         ${vpf}openstring += p + ',';
1734         }
1735         ${vpf}openstring = ${vpf}openstring.replace(/,+\$/, '');
1736         return;
1737 }
1738 function ${vpf}do_explode () {
1739         for(var i = 0; i < ${vpf}lines.length; i++)
1740                 ${vpf}openstatus[i] = 1;
1741         ${vpf}gen_openstring();
1742         ${vpf}rewrite_tree();
1743 }
1744 function ${vpf}do_collapse () {
1745         for(var i = 0; i < ${vpf}lines.length; i++)
1746                 ${vpf}openstatus[i] = 0;
1747         ${vpf}gen_openstring();
1748         ${vpf}rewrite_tree();
1749 }
1750 function ${vpf}rewrite_tree () {
1751         var thing = '';
1752         for(i = 0; i < ${vpf}lines.length; i++) {
1753                 thing = thing + ${vpf}tree_link(i);
1754         }
1755         ${vpf}treebox.innerHTML = thing;
1756         ${vpf}next_level = 0;
1757 }
1758
1759 // END CLIP
1760
1761 EOF
1762
1763         push @out, <<EOF;
1764
1765 if(${vpf}collapse == 1 || ${vpf}explode == 1) {
1766         ${vpf}openstatus.length = 0;
1767 }
1768 for( var i = 0; i < ${vpf}lines.length; i++) {
1769         if(${vpf}openstatus[i] == undefined)
1770                 ${vpf}openstatus[i] = ${vpf}explode;
1771 }
1772
1773 ${vpf}collapse = 0;
1774 ${vpf}explode = 0;
1775 ${vpf}gen_openstring();
1776 ${vpf}rewrite_tree();
1777 </script>
1778 EOF
1779
1780         my $footer;
1781         $footer = ::interpolate_html($opt->{footer_template})
1782                 if $opt->{footer_template};
1783         if($footer =~ /\S/) {
1784                 $footer = Vend::Tags->uc_attr_list($opt, $footer);
1785                 push @out, $footer;
1786         }
1787
1788         return join "\n", @out;
1789 }
1790
1791
1792 my %menu_default_img = (
1793                 clear  => 'bg.gif',
1794                 closed => 'fc.gif',
1795                 open   => 'fo.gif',
1796 );
1797
1798 sub dhtml_browser {
1799         my $regex;
1800         eval {
1801                 $regex = $::Variable->{MV_DHTML_BROWSER}
1802                         and $regex = qr/$regex/;
1803         };
1804         $regex ||= qr/MSIE [5-9].*Windows|Mozilla.*Gecko|Opera.*[7-9]/;
1805         return $Vend::Session->{browser} =~ $regex;
1806 }
1807
1808 ## Returns a link line for a tree walk without DHTML.
1809 sub tree_link {
1810         my ($template, $row, $opt) = @_;
1811
1812         for(@{$opt->{_transform}}) {
1813                 return unless $transform{$_}->($row, $opt->{$_});
1814         }
1815
1816         $template ||= qq[
1817 {MV_SPACER}{MV_CHILDREN?}<a href="{TOGGLE_URL}" class="{TOGGLE_CLASS}" style="{TOGGLE_STYLE}">{TOGGLE_ANCHOR}</a>{PAGE?}<a href="{HREF}" class="{TOGGLE_CLASS}" style="{TOGGLE_STYLE}">{/PAGE?}{NAME}{PAGE?}</a>{/PAGE?}{/MV_CHILDREN?}{MV_CHILDREN:}{TOGGLE_ANCHOR}{PAGE?}<A href="{HREF}" class="{LINK_CLASS}" style="{LINK_STYLE}">{/PAGE?}{NAME}{PAGE?}</a>{/PAGE?}{/MV_CHILDREN:}<br$Vend::Xtrailer>
1818 ];
1819
1820         if(! $row->{page}) {
1821         }
1822         elsif ($row->{page} =~ /^\w+:/ or $row->{page} =~ m{^/}) {
1823                 $row->{href} = $row->{page};
1824         }
1825         else {
1826                 unless($row->{form} =~ /[\r\n]/) {
1827                         $row->{form} = join "\n", split $Global::UrlSplittor, $row->{form};
1828                 }
1829                 my $add = ($::Scratch->{mv_add_dot_html} && $row->{page} !~ /\.\w+$/) || 0;
1830                 $row->{href} = Vend::Tags->area({
1831                                                         href => $row->{page},
1832                                                         form => $row->{form},
1833                                                         add_dot_html => $add,
1834                                                         auto_format => 1,
1835                                                 });
1836         }
1837         $row->{name} =~ s/ /&nbsp;/g;
1838         $opt->{toggle_base_url} ||= Vend::Tags->history_scan(
1839                                                         { var_exclude => 'toggle,collapse,expand' }
1840                                                         );
1841         $row->{link_class} ||= $opt->{link_class};
1842         $row->{link_style} ||= $opt->{link_style};
1843         if($row->{mv_children}) {
1844                 my $u = $opt->{toggle_base_url};
1845                 $u .= $u =~ /\?/ ? $Global::UrlJoiner : "?";
1846                 $u .= "toggle=$row->{code}";
1847                 $row->{toggle_url} = $u;
1848                 if($row->{mv_toggled}) {
1849                         $row->{toggle_anchor} = $opt->{toggle_anchor_open};
1850                         $row->{toggle_class}  = $opt->{link_class_open};
1851                         $row->{toggle_style}  = $opt->{link_style_open};
1852                 }
1853                 else {
1854                         $row->{toggle_anchor} = $opt->{toggle_anchor_closed};
1855                         $row->{toggle_class}  = $opt->{link_class_closed};
1856                         $row->{toggle_style}  = $opt->{link_style_closed};
1857                 }
1858         }
1859         else {
1860                 $row->{toggle_anchor} = $opt->{toggle_anchor_clear};
1861         }
1862         extra_value($opt->{extra_value}, $row)
1863                         if $opt->{extra_value};
1864         return Vend::Tags->uc_attr_list($row, $template);
1865 }
1866
1867 ## Returns a javascript line from a tree walk.
1868 ## Designed as a [tree ..] iterator, first iteration
1869 ## returns UPPERCASE var name index defines for the fields.
1870 sub tree_line {
1871         my($template, $row, $opt) = @_;
1872
1873         my @out;
1874         my $fields;
1875
1876         if (! defined $opt->{loopinc}) {
1877                 my $vpf = $opt->{js_prefix} || 'mv_';
1878                 $opt->{loopinc} = 0;
1879                 $opt->{loopname} ||= $vpf . 'lines';
1880                 $fields = [qw/  code
1881                                                         parent_fld
1882                                                         mv_level
1883                                                         mv_children
1884                                                         mv_increment
1885                                                         page
1886                                                         form
1887                                                         name
1888                                                         description
1889                                                         img_up
1890                                                         img_dn
1891                                                         img_sel
1892                                                         target
1893                                                         / ];
1894                 if($opt->{loopfields}) {
1895                         if(! ref($opt->{loopfields})) {
1896                                 my $fstring = $opt->{loopfields};
1897                                 $fstring =~ s/^\s+//;
1898                                 @$fields = split /[\s,\0]+/, $fstring;
1899                         }
1900                         else {
1901                                 $fields = $opt->{loopfields};
1902                         }
1903                 }
1904
1905                 if($opt->{fields_repository}) {
1906                         $opt->{fields_repository} = [ @$fields ];
1907                 }
1908                 push @$fields, 'open';
1909                 for(my $i = 1; $i < @$fields; $i++) {
1910                         push @out, "var $vpf\U$fields->[$i]\E = $i;";
1911                 }
1912                 pop @$fields;
1913                 $opt->{loopfields} = $fields;
1914         }
1915
1916         $fields = $opt->{loopfields};
1917
1918         if(defined $opt->{next_level}) {
1919                 return if $row->{mv_level} > $opt->{next_level};
1920                 undef $opt->{next_level};
1921         }
1922
1923         for(@{$opt->{_transform}}) {
1924                 my $status = $transform{$_}->($row, $opt->{$_});
1925                 $opt->{next_level} = $row->{mv_level}
1926                         if ! $status;
1927                 return unless $status;
1928         }
1929
1930         if($row->{page} and $row->{page} !~ m{^(\w+:)?/}) {
1931                 my $form = $row->{form};
1932                 if($form and $form !~ /[\r\n]/) {
1933                         $form = join "\n", split $Global::UrlSplittor, $form;
1934                 }
1935
1936                 my $add = ($::Scratch->{mv_add_dot_html} && $row->{page} !~ /\.\w+$/) || 0;
1937
1938                 $row->{page} = Vend::Tags->area({
1939                                                                 href => $row->{page},
1940                                                                 form => $form,
1941                                                                 no_count => $opt->{timed},
1942                                                                 add_dot_html => $add,
1943                                                                 no_session_id => $opt->{timed},
1944                                                                 auto_format => 1,
1945                                                         });
1946
1947                 unless($opt->{no_open}) {
1948                         if($row->{page} =~ m{\?.+=}) {
1949                                 $row->{page} .= "$Global::UrlJoiner$opt->{open_variable}=";
1950                         }
1951                         else {
1952                                 $row->{page} .= "?$opt->{open_variable}=";
1953                         }
1954                 }
1955         }
1956
1957         my @values = @{$row}{@$fields};
1958
1959         for(@values) {
1960                 $_ = Vend::Tags->jsq($_) unless $_ eq '0' || /^[1-9](?:\d*\.)?\d*$/;
1961         }
1962         push @out, "$opt->{loopname}\[" . $opt->{loopinc}++  . "] = [" . join(", ", @values) . "];";
1963         return join "\n", @out, '';
1964 }
1965
1966 sub transforms_only {
1967         my ($template, $row, $opt) = @_;
1968
1969         my %line;
1970         if(ref($row) eq 'ARRAY') {
1971                 $opt->{_fa} ||= $opt->{object}{mv_field_names};
1972                 @line{@{$opt->{_fa}}} = @$row;
1973                 $row = \%line;
1974         }
1975
1976         for(@{$opt->{_transform}}) {
1977                 $row->{deleted} = 1, return unless $transform{$_}->($row, $opt->{$_});
1978         }
1979         return;
1980 }
1981
1982
1983 sub menu_link {
1984         my ($template, $row, $opt) = @_;
1985
1986         # Set to a default if not passed
1987         $template ||= <<EOF unless $template =~ /\S/;
1988 {PAGE:}
1989         <b>{NAME}:</b>
1990         <br$Vend::Xtrailer>
1991 {/PAGE:}
1992
1993 {PAGE?}
1994 &nbsp;&nbsp;&nbsp;
1995 <a href="{HREF}"{DESCRIPTION?} title="{DESCRIPTION}"{/DESCRIPTION?}>{NAME}</a><br$Vend::Xtrailer>
1996 {/PAGE?}
1997 EOF
1998
1999         my %line;
2000         if(ref($row) eq 'ARRAY') {
2001                 $opt->{_fa} ||= $opt->{object}{mv_field_names};
2002                 @line{@{$opt->{_fa}}} = @$row;
2003                 $row = \%line;
2004         }
2005
2006         $row->{mv_ip} = $opt->{mv_ip}++ || 0;
2007         $row->{mv_increment} = ++$opt->{mv_increment};
2008
2009         for(@{$opt->{_transform}}) {
2010                 return unless $transform{$_}->($row, $opt->{$_});
2011         }
2012
2013         #return $row->{name} if ! $row->{page} and $row->{name} =~ /^\s*</;
2014         if(! $row->{page}) {
2015         }
2016         elsif ($row->{page} =~ /^\w+:/) {
2017                 $row->{href} = $row->{page};
2018         }
2019         else {
2020                 unless($row->{form} =~ /[\r\n]/) {
2021                         $row->{form} = join "\n", split $Global::UrlSplittor, $row->{form};
2022                 }
2023                 my $add = $::Scratch->{mv_add_dot_html} && $row->{page} !~ /\.\w+$/;
2024
2025                 $row->{href} = Vend::Tags->area(
2026                                                                 {
2027                                                                         href => $row->{page},
2028                                                                         form => $row->{form},
2029                                                                         add_dot_html => $add,
2030                                                                         auto_format => 1,
2031                                                                 });
2032         }
2033         extra_value($opt->{extra_value}, $row)
2034                         if $opt->{extra_value};
2035         return Vend::Tags->uc_attr_list($row, $template);
2036 }
2037
2038 sub annfile {
2039         my $fn = shift;
2040         my $afn = $fn;
2041         $afn =~ s{(.*)/(.*)}{$2};
2042         my $base = $afn;
2043         if(my $dir = $1) {
2044                 $afn = "...$afn";
2045                 $afn = join "/", $dir, $afn;
2046         }
2047         else {
2048                 $afn = "...$afn";
2049         }
2050         return $base unless -f $afn and -r _;
2051         
2052         open AFILE, "< $afn"
2053                 or die "Cannot open annotation file $afn: $!\n";
2054         my $text = join "", <AFILE>;
2055         close AFILE;
2056         $text =~ s/^\s+//;
2057         $text =~ s/\s+$//;
2058         return $text;
2059 }
2060
2061 sub make_tree_from_directory {
2062         my ($dir, $level, $prepend, $outfile) = @_;
2063         my @files = glob "$dir/*";
2064         my @out;
2065         $prepend ||= '';
2066         local $/;
2067         for(@files) {
2068                 my %record;
2069                 $record{msort} = $level;
2070                 $record{name} = annfile($_);
2071                 $record{description} = $_;
2072                 if(-d $_) {
2073                         push @out, \%record;
2074                         push @out, make_tree_from_directory($_, $level + 1, $prepend);
2075                 }
2076                 else {
2077                         if($prepend) {
2078                                 my $fn = $_;
2079                                 $fn =~ s:^/*[^/]+?/::;
2080                                 $record{page} = "$prepend$fn";
2081                         }
2082                         else {
2083                                 $record{page} = $_;
2084                         }
2085                         push @out, \%record;
2086                 }
2087         }
2088
2089         return @out unless $outfile;
2090
2091         open OUT, "> $outfile"
2092                 or do {
2093                         logError("Couldn't write outfile %s: %s", $outfile, $!);
2094                         return undef;
2095                 };
2096
2097         my @fields = qw/msort name page description/;
2098         print OUT join("\t", 'code', @fields);
2099         print OUT "\n";
2100         my $code = '0001';
2101         for(@out) {
2102                 print OUT join "\t", $code++, @{$_}{@fields};
2103                 print OUT "\n";
2104         }
2105         close OUT;
2106 }
2107
2108 sub open_script {
2109         my $opt = shift;
2110         my $vpf = $opt->{js_prefix} || 'mv_';
2111
2112         my $out = "<script>\n${vpf}openstatus = [";
2113         my @open =  split /,/, $CGI::values{$opt->{open_variable} || 'open'};
2114         my @o;
2115
2116         my %hsh = (map { ($_, 1) } @open);
2117
2118         for(0 .. $open[$#open]) {
2119                 push @o, ($hsh{$_} ? 1 : 0);
2120         }
2121         $out .= join ",", @o;
2122         $out .= "];\n";
2123         $out .= "${vpf}explode = ";
2124         $out .= $CGI::values{$opt->{explode_variable} || 'explode'} ? 1 : 0;
2125         $out .= ";\n";
2126         $out .= "${vpf}collapse = ";
2127         $out .= $CGI::values{$opt->{collapse_variable} || 'collapse'} ? 1 : 0;
2128         $out .= ";\n";
2129         $out .= "${vpf}gen_openstring();\n";
2130         $out .= "${vpf}rewrite_tree();\n</script>";
2131 }
2132
2133 sub menu {
2134         my ($name, $opt, $template) = @_;
2135
2136         if($opt->{open_script}) {
2137                 return open_script($opt);
2138         }
2139         
2140         Vend::Tags->tmp('mv_logical_page_used', $::Scratch->{mv_logical_page_used});
2141         reset_transforms($opt);
2142
2143         if(! $name and ! $opt->{list}) {
2144                 # Auto menu for pages
2145                 if($::Scratch->{mv_menu}) {
2146                         my @names= qw/code page form anchor description/;
2147                         my $i = 0;
2148                         my %hash = map { ( $_, $i++) } @names;
2149                         my $code = '000';
2150                         my @rows;
2151                         my @items = split m{(?:</li\s*>)\s*<li>\s*}i, $::Scratch->{mv_menu};
2152                         for(@items) {
2153                                 my ($page, $anchor, $form, $desc);
2154                                 m{
2155                                         <a \s+
2156                                                 (?:[^>]+\s+)?
2157                                                 title \s*=\s*
2158                                                 (["']) # mandatory quote
2159                                                         ([^"'>\s]+)
2160                                                 \1      # end quote
2161                                         }isx and $desc = $2;
2162                                 m{
2163                                         <a \s+
2164                                                 (?:[^>]+\s+)?
2165                                                 href \s*=\s*
2166                                                 (["']?) # possible quote
2167                                                         ([^"'>\s]+)
2168                                                 \1      # end quote
2169                                         }isx and $page = $2;
2170                                 ($page, $form) = split /\?/, $page, 2
2171                                         if $page;
2172                                 s{<a\s+.*?>}{}is;
2173                                 s{</a>}{}i;
2174                                 push @rows, [ $code++, $page, $form, $anchor, $desc ];
2175                         }
2176                         $opt->{list} = [ \@rows, \%hash, \@names ];
2177
2178                 }
2179                 else {
2180                         my $page_name = $Global::Variable->{MV_PAGE};
2181                         my $dir = Vend::Tags->var('MV_MENU_DIRECTORY', 2) || 'include/menus';
2182                         while($page_name =~ s:/[^/]+$::) {
2183                                 my $fn = "$dir/auto/$page_name.txt";
2184 #::logDebug("page name=$page_name, testing for $fn");
2185                                 if(-f $fn) {
2186                                         $opt->{file} = $fn;
2187                                         last;
2188                                 }
2189                         }
2190                         if(! $opt->{file} and -f "$dir/default.txt") {
2191                                 $opt->{file} = "$dir/default.txt";
2192                         }
2193                 }
2194         }
2195
2196         $opt->{dhtml_browser} = dhtml_browser()
2197                 unless defined $opt->{dhtml_browser};
2198         $opt->{menu_type} ||= 'simple';
2199
2200         my $prefix = $opt->{prefix} || 'menu';
2201         $opt->{link_class} ||= $::Variable->{MV_DEFAULT_LINK_CLASS};
2202
2203         $opt->{parse_header_footer} = 1 unless defined $opt->{parse_header_footer};
2204
2205         if($opt->{parse_header_footer}) {
2206                 $opt->{parse_header} = $opt->{parse_footer} = 1;
2207         }
2208         if($template and $template =~ s:\[$prefix-header\](.*?)\[/$prefix-header\]::si) {
2209                 $opt->{header_template} = $1;
2210         }
2211         if($template and $template =~ s:\[$prefix-footer\](.*?)\[/$prefix-footer\]::si) {
2212                 $opt->{footer_template} = $1;
2213         }
2214
2215         my @transform;
2216         my @ordered_transform = qw/full_interpolate indicator_page page_class indicator_class localize entities nbsp/;
2217         my %ordered;
2218         @ordered{@ordered_transform} = @ordered_transform;
2219
2220         for(keys %transform) {
2221                 next if $ordered{$_};
2222                 next unless $opt->{$_};
2223                 my @fields = grep /\S/, split /[\s,\0]+/, $opt->{$_};
2224                 $opt->{$_} = \@fields;
2225                 push @transform, $_;
2226         }
2227         for(@ordered_transform) {
2228                 next unless $opt->{$_};
2229                 my @fields = grep /\S/, split /[\s,\0]+/, $opt->{$_};
2230                 $opt->{$_} = \@fields;
2231                 push @transform, $_;
2232         }
2233         $opt->{_transform} = \@transform;
2234
2235         if($opt->{menu_type} eq 'tree') {
2236                 $opt->{link_class_open}   ||= $opt->{link_class};
2237                 $opt->{link_class_closed} ||= $opt->{link_class};
2238                 if(is_yes($opt->{no_image})) {
2239                         $opt->{no_image} = 1;
2240                         $opt->{toggle_anchor_clear}  ||= '&nbsp;';
2241                         $opt->{toggle_anchor_closed} ||= '+';
2242                         $opt->{toggle_anchor_open}   ||= '-';
2243                 }
2244                 else {
2245                         $opt->{no_image} = 0;
2246                         my $nm = "img_$_";
2247                         if($opt->{file_tree}) {
2248                                 for(qw/ no_open no_wrap /) {
2249                                         $opt->{$_} = 1 unless defined $opt->{$_};
2250                                 }
2251
2252                                 $opt->{img_open} ||= 'openfolder.gif';
2253                                 $opt->{img_closed} ||= 'closedfolder.gif';
2254                         }
2255
2256                         $opt->{toggle_anchor_open} = Vend::Tags->image( {
2257                                                         src => $opt->{img_open}  || $menu_default_img{open},
2258                                                         border => 0,
2259                                                         extra => $opt->{img_open_extra} || 'align=absbottom',
2260                                                         });
2261                         $opt->{toggle_anchor_closed} = Vend::Tags->image( {
2262                                                         src => $opt->{img_closed} || $menu_default_img{closed},
2263                                                         border => 0,
2264                                                         extra => $opt->{img_closed_extra} || 'align=absbottom',
2265                                                         });
2266                         if($opt->{toggle_anchor_closed} =~ /\s+width="?(\d+)/i) {
2267                                 $opt->{img_clear_extra} ||= "height=1 width=$1";
2268                         }
2269                         $opt->{toggle_anchor_clear} = Vend::Tags->image( {
2270                                                         src => $opt->{img_clear} || $menu_default_img{clear},
2271                                                         getsize => 0,
2272                                                         border => 0,
2273                                                         extra => $opt->{img_clear_extra},
2274                                                         });
2275 #::logDebug("toggle_anchor_clear=$opt->{toggle_anchor_clear}");
2276                 }
2277
2278                 if($opt->{use_file}) {
2279                         $opt->{file} = $::Variable->{MV_MENU_DIRECTORY} || 'include/menus';
2280                         if(! $opt->{name}) {
2281                                 logError("No file or name specified for menu.");
2282                         }
2283                         my $nm = escape_chars($opt->{name});
2284                         $opt->{file} .= "/$nm.txt";
2285                         undef $opt->{file} unless -f $opt->{file};
2286                 }
2287                 elsif($opt->{directory}) {
2288                         my $d = "$Vend::Cfg->{ScratchDir}/filetree";
2289                         mkdir $d, 0777 unless -d $d;
2290                         $opt->{file} = "$Vend::Cfg->{ScratchDir}/filetree/$Vend::SessionID.txt";
2291                         make_tree_from_directory(
2292                                                                         $opt->{directory},
2293                                                                         0,
2294                                                                         delete $opt->{link_prepend},
2295                                                                         $opt->{file},
2296                                                                 )
2297                                 or do {
2298                                         logError("Unable to make tree from directory %s", $opt->{directory});
2299                                         return;
2300                                 };
2301                 }
2302
2303                 return old_tree($name,$opt,$template) unless $opt->{dhtml_browser};
2304                 return file_tree($name,$opt,$template) if $opt->{file_tree};
2305                 return dhtml_tree($name,$opt,$template);
2306         }
2307         elsif($opt->{menu_type} eq 'flyout') {
2308                 $opt->{link_class_open}   ||= $opt->{link_class};
2309                 $opt->{link_class_closed} ||= $opt->{link_class};
2310                 if(is_yes($opt->{no_image})) {
2311                         $opt->{no_image} = 1;
2312                         $opt->{toggle_anchor_clear}  ||= '&nbsp;';
2313                         $opt->{toggle_anchor_closed} ||= '+';
2314                         $opt->{toggle_anchor_open}   ||= '-';
2315                 }
2316                 else {
2317                         $opt->{no_image} = 0;
2318                         my $nm = "img_$_";
2319                         $opt->{toggle_anchor_open} = Vend::Tags->image( {
2320                                                         src => $opt->{img_open}  || $menu_default_img{open},
2321                                                         border => 0,
2322                                                         extra => $opt->{img_open_extra} || 'align="absbottom"',
2323                                                         });
2324                         $opt->{toggle_anchor_closed} = Vend::Tags->image( {
2325                                                         src => $opt->{img_closed} || $menu_default_img{closed},
2326                                                         border => 0,
2327                                                         extra => $opt->{img_closed_extra} || 'align="absbottom"',
2328                                                         });
2329                         if($opt->{toggle_anchor_closed} =~ /\s+width="?(\d+)/i) {
2330                                 $opt->{img_clear_extra} ||= qq{height="1" width="$1"};
2331                         }
2332                         $opt->{toggle_anchor_clear} = Vend::Tags->image( {
2333                                                         src => $opt->{img_clear} || $menu_default_img{clear},
2334                                                         getsize => 0,
2335                                                         border => 0,
2336                                                         extra => $opt->{img_clear_extra},
2337                                                         });
2338                 }
2339                 if($opt->{use_file}) {
2340                         $opt->{file} = $::Variable->{MV_MENU_DIRECTORY} || 'include/menus';
2341                         if(! $opt->{name}) {
2342                                 logError("No file or name specified for menu.");
2343                         }
2344                         my $nm = escape_chars($opt->{name});
2345                         $opt->{file} .= "/$nm.txt";
2346                         undef $opt->{file} unless -f $opt->{file};
2347                 }
2348
2349                 return old_flyout($name,$opt,$template) unless $opt->{dhtml_browser};
2350                 return dhtml_flyout($name,$opt,$template);
2351         }
2352         elsif($opt->{menu_type} eq 'simple') {
2353                 if($opt->{search} || $opt->{list}) {
2354                         ## Do nothing
2355                 }
2356                 elsif(! $opt->{file}) {
2357                         $opt->{file} = $::Variable->{MV_MENU_DIRECTORY} || 'include/menus';
2358                         if(! $opt->{name}) {
2359                                 logError("No file or name specified for menu.");
2360                         }
2361                         my $nm = escape_chars($opt->{name});
2362                         $opt->{file} .= "/$nm.txt";
2363                 }
2364                 return old_simple($name, $opt, $template) unless $opt->{dhtml_browser};
2365                 return dhtml_simple($name, $opt, $template);
2366         }
2367         else {
2368                 logError("unknown menu_type %s", $opt->{menu_type});
2369         }
2370 }
2371
2372
2373 1;
2374 __END__