1# Vend::Table::Editor - Swiss-army-knife table editor for Interchange 2# 3# $Id: Editor.pm,v 1.93 2009-03-20 18:59:35 mheins Exp $ 4# 5# Copyright (C) 2002-2008 Interchange Development Group 6# Copyright (C) 2002 Mike Heins <mike@perusion.net> 7# 8# This program was originally based on Vend 0.2 and 0.3 9# Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com> 10# 11# This program is free software; you can redistribute it and/or modify 12# it under the terms of the GNU General Public License as published by 13# the Free Software Foundation; either version 2 of the License, or 14# (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public 22# License along with this program; if not, write to the Free 23# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 24# MA 02110-1301 USA. 25 26package Vend::Table::Editor; 27 28use vars qw($VERSION); 29$VERSION = substr(q$Revision: 1.93 $, 10); 30 31use Vend::Util; 32use Vend::Interpolate; 33use Vend::Data; 34use Exporter; 35@EXPORT_OK = qw/meta_record expand_values tabbed_display display/; 36use strict; 37no warnings qw(uninitialized numeric); 38 39=head1 NAME 40 41Vend::Table::Editor -- Interchange do-all HTML table editor 42 43=head1 SYNOPSIS 44 45[table-editor OPTIONS] 46 47[table-editor OPTIONS] TEMPLATE [/table-editor] 48 49=head1 DESCRIPTION 50 51The [table-editor] tag produces an HTML form that edits a database 52table or collects values for a "wizard". It is extremely configurable 53as to display and characteristics of the widgets used to collect the 54input. 55 56The widget types are based on the Interchange C<[display ...]> UserTag, 57which in turn is heavily based on the ITL core C<[accessories ...]> tag. 58 59The C<simplest> form of C<[table-editor]> is: 60 61 [table-editor table=foo] 62 63A page which contains only that tag will edit the table C<foo>, where 64C<foo> is the name of an Interchange table to edit. If no C<foo> table 65is C<defined>, then nothing will be displayed. 66 67If the C<mv_metadata> entry "foo" is present, it is used as the 68definition for table display, including the fields to edit and labels 69for sections of the form. If C<ui_data_fields> is defined, this 70cancels fetch of the view and any breaks and labels must be 71defined with C<ui_break_before> and C<ui_break_before_label>. More 72on the view concept later. 73 74A simple "wizard" can be made with: 75 76 [table-editor 77 wizard=1 78 ui_wizard_fields="foo bar" 79 mv_nextpage=wizard2 80 mv_prevpage=wizard_intro 81 ] 82 83The purpose of a "wizard" is to collect values from the user and 84place them in the $Values array. A next page value (option mv_nextpage) 85must be defined to give a destination; if mv_prevpage is defined then 86a "Back" button is presented to allow paging backward in the wizard. 87 88=cut 89 90my $Tag = new Vend::Tags; 91 92my $Trailer; 93 94use vars qw/%Display_type %Display_options %Style_sheet/; 95 96%Display_options = ( 97 three_column => sub { 98 my $opt = shift; 99 $opt->{cell_span} = 3; 100 return; 101 }, 102 nospace => sub { 103 my $opt = shift; 104 $opt->{break_cell_first_style} ||= 'border-top: 1px solid #999999'; 105 return; 106 }, 107 adjust_width => sub { 108 my $opt = shift; 109 $opt->{adjust_cell_class} ||= $opt->{widget_cell_class}; 110 if($opt->{table_width} and $opt->{table_width} =~ /^\s*(\d+)(\w*)\s*$/) { 111 my $wid = $1; 112 my $type = $2 || ''; 113 $opt->{help_cell_style} ||= 'width: ' . int($wid * .35) . $type; 114 } 115 else { 116 $opt->{help_cell_style} ||= 'width: 400'; 117 } 118 return; 119 }, 120); 121 122%Display_type = ( 123 default => sub { 124 my $opt = shift; 125 my $thing = <<EOF; 126 <td$opt->{label_cell_extra}> 127 {BLABEL}{LABEL}{ELABEL}{META_STRING} 128 </td> 129 <td$opt->{data_cell_extra}\{COLSPAN}> 130 <table cellspacing="0" cellmargin="0" width="100%"> 131 <tr> 132 <td$opt->{widget_cell_extra}> 133 {WIDGET} 134 </td> 135 <td$opt->{help_cell_extra}>{TKEY}{HELP?}<i>{HELP}</i>{/HELP?}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td> 136 </tr> 137 </table> 138 </td> 139EOF 140 chomp $thing; 141 return $thing; 142 }, 143 blank => sub { 144 my $opt = shift; 145 my $thing = <<EOF; 146EOF 147 chomp $thing; 148 return $thing; 149 150 }, 151 nospace => sub { 152 my $opt = shift; 153 my $span = shift; 154 $opt->{break_template} ||= <<EOF; 155<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr> 156EOF 157 my $thing = <<EOF; 158 <td$opt->{label_cell_extra}> 159 {BLABEL}{LABEL}{ELABEL} 160 </td> 161 <td$opt->{data_cell_extra}\{COLSPAN}> 162 <table cellspacing="0" cellmargin="0" width="100%"> 163 <tr> 164 <td$opt->{widget_cell_extra}> 165 {WIDGET} 166 </td> 167 <td$opt->{help_cell_extra}>{TKEY}{HELP?}<i>{HELP}</i>{/HELP?}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td> 168 <td align="right">{META_STRING}</td> 169 </tr> 170 </table> 171 </td> 172EOF 173 chomp $thing; 174 return $thing; 175 176 }, 177 text_js_help => sub { 178 my $opt = shift; 179 my $thing = <<EOF; 180 <td$opt->{label_cell_extra}> 181 {BLABEL}{LABEL}{ELABEL} 182 </td> 183 <td$opt->{data_cell_extra}\{COLSPAN} nowrap>{WIDGET}{HELP_EITHER?} <a href="{HELP_URL?}{HELP_URL}{/HELP_URL?}{HELP_URL:}javascript:alert('{HELP}'); void(0){/HELP_URL:}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?} {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?} 184 </td> 185EOF 186 chomp $thing; 187 return $thing; 188 }, 189 three_column => sub { 190 my $opt = shift; 191 my $thing = <<EOF; 192 <td$opt->{label_cell_extra}> 193 {BLABEL}{LABEL}{ELABEL} 194 </td> 195 <td$opt->{data_cell_extra}\{COLSPAN} nowrap> 196 {WIDGET} 197 </td> 198 <td>{HELP_EITHER?} <a href="{HELP_URL}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?} {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?} 199 </td> 200EOF 201 chomp $thing; 202 return $thing; 203 }, 204 simple_row => sub { 205#::logDebug("calling simple_row display"); 206 my $opt = shift; 207 my $thing = <<EOF; 208 <td$opt->{label_cell_extra}> 209 {BLABEL}{LABEL}{ELABEL} 210 </td> 211 <td$opt->{data_cell_extra}\{COLSPAN} nowrap>{WIDGET}{HELP_EITHER?} <a href="{HELP_URL}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?} {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?} 212 </td> 213EOF 214 chomp $thing; 215 return $thing; 216 }, 217 over_under => sub { 218 my $opt = shift; 219 my $thing = <<EOF; 220{HELP?} 221 <td colspan="2"$opt->{help_cell_extra}> 222 {HELP} 223 </td> 224</tr> 225<tr> 226{/HELP?} <td colspan="2"$opt->{label_cell_extra}> 227 {LABEL} 228 </td> 229</tr> 230<tr> 231 <td colspan="2"$opt->{widget_cell_extra}> 232 {WIDGET} 233 </td> 234EOF 235 chomp $thing; 236 return $thing; 237 }, 238 adjust_width => sub { 239 my $opt = shift; 240 my $span = shift; 241 $opt->{break_template} ||= <<EOF; 242$opt->{spacer_row} 243<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr> 244EOF 245 my $thing = <<EOF; 246 <td$opt->{label_cell_extra}> 247 {BLABEL}{LABEL}{ELABEL} 248 </td> 249 <td$opt->{data_cell_extra}\{COLSPAN}> 250 <table cellspacing="0" cellmargin="0" width="100%"> 251 <tr> 252 <td$opt->{widget_cell_extra}> 253 {WIDGET} 254 </td> 255 <td$opt->{help_cell_extra}>{TKEY}{HELP?}{HELP}{/HELP?}{HELP:} {/HELP:}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td> 256 <td align="right">{META_STRING}</td> 257 </tr> 258 </table> 259 </td> 260EOF 261 chomp $thing; 262 return $thing; 263 }, 264 image_meta => sub { 265 my $opt = shift; 266 my $span = shift; 267 $opt->{break_template} ||= <<EOF; 268$opt->{spacer_row} 269<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr> 270EOF 271 my $thing = <<EOF; 272 <td$opt->{label_cell_extra}> 273 {BLABEL}{LABEL}{ELABEL} 274 </td> 275 <td$opt->{data_cell_extra}\{COLSPAN}> 276 <table cellspacing="0" cellmargin="0" width="100%"> 277 <tr> 278 <td$opt->{widget_cell_extra}> 279 {WIDGET} 280 </td> 281 <td$opt->{help_cell_extra}>{TKEY}{HELP?}{HELP}{/HELP?}{HELP:} {/HELP:}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td> 282 <td align="right">{META_STRING}</td> 283 </tr> 284 </table> 285 </td> 286EOF 287 chomp $thing; 288 return $thing; 289 }, 290 simple_help_below => sub { 291 my $opt = shift; 292 my $thing = <<EOF; 293 <td$opt->{label_cell_extra}> 294 {BLABEL}{LABEL}{ELABEL} 295 </td> 296 <td$opt->{data_cell_extra}\{COLSPAN}> 297 {WIDGET} 298 {HELP_EITHER?}<br$Trailer>{/HELP_EITHER?} 299 {HELP}{HELP_URL?}<br$Trailer><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?} 300 {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?} 301 </td> 302 </tr> 303 </table> 304 </td> 305EOF 306 chomp $thing; 307 return $thing; 308 }, 309 simple_icon_help => sub { 310 my $opt = shift; 311 $opt->{help_icon} ||= '/icons/small/unknown.gif'; 312 my $thing = <<EOF; 313 <td$opt->{label_cell_extra}> 314 {BLABEL}{LABEL}{ELABEL} 315 </td> 316 <td$opt->{data_cell_extra}\{COLSPAN}> 317 <table width="100%"> 318 <tr> 319 <td style="padding-left: 3px"> 320 {WIDGET} 321 </td> 322 <td align="right"> 323 {HELP_EITHER?} <a href="{HELP_URL?}{HELP_URL}{/HELP_URL?}{HELP_URL:}javascript:alert('{HELP}'); void(0){/HELP_URL:}" title="{HELP}"><img src="$opt->{help_icon}" border="0"></a>{/HELP_EITHER?} {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?} 324 </td> 325 </tr> 326 </table> 327 </td> 328EOF 329 chomp $thing; 330 return $thing; 331 }, 332); 333 334my %dt_map = qw/ 335 simple_row 1 336 text_help 2 337 simple_icon_help 3 338 over_under 4 339 simple_help_below 5 340 image_meta 6 341 three_column 7 342 nospace 8 343/; 344 345for(keys %dt_map) { 346 $Display_type{$dt_map{$_}} = $Display_type{$_} 347 if $Display_type{$_}; 348 $Display_options{$dt_map{$_}} = $Display_options{$_} 349 if $Display_options{$_}; 350} 351 352%Style_sheet = ( 353 default => <<EOF, 354<style type="text/css"> 355.rborder { 356 background-color: #CCCCCC; 357 margin: 0;; 358 padding: 2 359} 360 361.rhead { 362 background-color: #E6E6E6;; 363 color: #000000; 364 font-family: Arial, Helvetica, sans-serif; 365 font-size: 12px 366} 367 368A.rhead:active,A.rhead:hover { 369 color: #000000; 370 font-size: 12px; 371 text-decoration: underline; 372} 373 374A.rhead:link,A.rhead:visited { 375 color: #000000; 376 font-size: 12px; 377 text-decoration:none; 378} 379 380.rheadBold { 381 background-color: #E6E6E6; 382 color: #000000; 383 font-family: Arial, Helvetica, sans-serif; 384 font-size: 12px; 385 font-weight: bold;; 386 padding: 4px 387} 388 389.rheader { 390 background-color: #999999; 391 color: #663333; 392} 393 394.rmarq { 395 background-color: #999999;; 396 color: #FFFFFF; 397 font-size: 12px; 398 font-weight: bold 399} 400 401A.rmarq:active,A.rmarq:link,A.rmarq:visited { 402 color: #FFFFCC; 403 font-size: 12px; 404 font-weight: bold; 405 text-decoration:none; 406} 407 408A.rmarq:hover { 409 color: #FFFF99; 410 font-size: 12px; 411 font-weight: bold; 412 text-decoration: underline; 413} 414 415.rnobg { 416 color: #000000; 417 font-family: Arial, Helvetica, sans-serif; 418 font-size: 12px; 419 padding: 4px; 420} 421 422.rnorm { 423 background-color: #FFFFFF; 424 border: 1px solid #CCCCCC; 425} 426 427.rowalt { 428 background-color: #EAF1FB; 429 color: #000000; 430 font-family: Arial, Helvetica, sans-serif; 431 font-size: 12px; 432 padding: 4px; 433} 434 435A.rowalt:hover,A.rowalt:hover,A.rownorm:active,A.rownorm:hover { 436 color: #333333; 437 font-family: Arial, Helvetica, sans-serif; 438 font-size: 12px; 439 text-decoration: underline; 440} 441 442A.rowalt:link,A.rowalt:visited,A.rownorm:link,A.rownorm:visited { 443 color: #333333; 444 font-family: Arial, Helvetica, sans-serif; 445 font-size: 12px; 446 text-decoration:none; 447} 448 449.rownorm { 450 background-color: #FFFFFF; 451 color: #000000; 452 font-family: Arial, Helvetica, sans-serif; 453 font-size: 12px; 454 padding: 4px; 455} 456 457.rownormbold { 458 background-color: #FFFFFF; 459 color: #000000; 460 font-family: Arial, Helvetica, sans-serif; 461 font-size: 12px; 462 font-weight: bold;; 463 padding: 4px 464} 465 466.rseparator { 467 background-color: #CCCCCC; 468} 469 470.rshade { 471 background-color: #E6E6E6; 472 color: #000000; 473 font-family: Arial, Helvetica, sans-serif; 474 font-size: 12px; 475 padding: 4px; 476} 477 478.rspacer { 479 background-color: #999999; 480 margin: 0;; 481 padding: 0 482} 483 484.rsubbold { 485 background-color: #FFFFFF; 486 color: #808080; 487 font-family: Arial, Helvetica, sans-serif; 488 font-size: 12px; 489 font-weight: bold;; 490 padding: 4px 491} 492 493.rtitle { 494 background-color: #808080;; 495 color: #FFFFFF; 496 font-family: Verdana, Arial, Helvetica, sans-serif; 497 font-size: 12px; 498 font-weight: bold 499} 500 501A.rtitle:active,A.rtitle:link,A.rtitle:visited { 502 color: #FFFFCC; 503 font-size: 12px; 504 font-weight: bold; 505 text-decoration:none; 506} 507 508 509.s1,.s2 { 510 color: #666666;; 511 font-family: Verdana, Arial, Helvetica, sans-serif; 512 font-size: 10px 513} 514 515.s3 { 516 color: #333333;; 517 font-family: Verdana, Arial, Helvetica, sans-serif; 518 font-size: 10px 519} 520 521.s4 { 522 color: #666666; 523 font-family: Verdana, Arial, Helvetica, sans-serif; 524 font-size: 10px; 525 width: 100%; 526} 527 528 529.rbreak { 530 background-color: #FFFFFF; 531} 532 533 534.cborder { 535 background-color: #999999; 536 padding: 0; 537} 538 539.cbreak { 540 background-color: #EEEEEE; 541 border-left: 1px solid #999999; 542 font-size: 11px;; 543 font-weight: bold 544} 545 546.cdata { 547 border-bottom: 1px solid #CCCCCC; 548 border-right: 1px solid #CCCCCC; 549 border-top: 1px solid #CCCCCC; 550 font-size: 11px;; 551 margin-right: 4px; 552 padding-right: 2px; 553 vertical-align: top 554} 555 556.cerror { 557 color: red; 558 font-size: 11px; 559} 560 561.cheader { 562 color: #663333; 563 font-size: 11px;; 564 font-weight: bold 565} 566 567.chelp,.rhint { 568 color: #AFABA5; 569 font-family: Arial, Helvetica, sans-serif; 570 font-size: 12px; 571 padding: 4px; 572} 573 574.clabel { 575 border-bottom: 1px solid #CCCCCC; 576 border-left: 1px solid #CCCCCC; 577 border-top: 1px solid #CCCCCC; 578 font-size: 11px; 579 vertical-align: top; 580 font-weight: medium; 581 padding-left: 5px;; 582 text-align: left 583} 584 585.cmiddle { 586 border-bottom: 1px solid #CCCCCC; 587 border-top: 1px solid #CCCCCC; 588 font-size: 11px; 589 font-weight: medium; 590 padding-left: 5px;; 591 text-align: middle 592} 593 594.cmessage { 595 color: green; 596 font-size: 11px; 597} 598 599A:link.ctitle,A:visited.ctitle { 600 color: white; 601 font-size: 11px;; 602 font-weight: bold; 603 text-decoration: none 604} 605 606A:hover.ctitle,A:active.ctitle { 607 color: yellow; 608 font-size: 11px;; 609 font-weight: bold; 610 text-decoration: underline 611} 612 613.ctitle { 614 font-size: 11px;; 615 font-weight: bold 616} 617 618.cwidget { 619 font-size: 11px;; 620 vertical-align: center 621} 622 623</style> 624EOF 625); 626 627sub expand_values { 628 my $val = shift; 629 return $val unless $val =~ /\[/; 630 $val =~ s/\[cgi\s+([^\[]+)\]/$CGI::values{$1}/ig; 631 $val =~ s/\[var\s+([^\[]+)\]/$::Variable->{$1}/ig; 632 $val =~ s/\[value\s+([^\[]+)\]/$::Values->{$1}/ig; 633 return $val; 634} 635 636sub widget_meta { 637 my ($type,$opt) = @_; 638 my $meta = meta_record("_widget::$type", $opt->{view}, $opt->{meta_table}, 1); 639 return $meta if $meta; 640 my $w = $Vend::Cfg->{CodeDef}{Widget}; 641 if($w and $w->{Widget}{$type}) { 642 my $string; 643 return undef unless $string = $w->{ExtraMeta}{$type}; 644 return get_option_hash($string); 645 } 646 647 $w = $Global::CodeDef->{Widget}; 648 if($w and $w->{Widget}{$type}) { 649 my $string; 650 return undef unless $string = $w->{ExtraMeta}{$type}; 651 return get_option_hash($string); 652 } 653 654 return $Vend::Form::ExtraMeta{$type}; 655} 656 657sub meta_record { 658 my ($item, $view, $mdb, $extended_only, $overlay) = @_; 659 660#::logDebug("meta_record: item=$item view=$view mdb=$mdb"); 661 return undef unless $item; 662 663 my $mtable; 664 if(! ref ($mdb)) { 665 $mtable = $mdb || $::Variable->{UI_META_TABLE} || 'mv_metadata'; 666#::logDebug("meta_record mtable=$mtable"); 667 $mdb = database_exists_ref($mtable) 668 or return undef; 669 } 670#::logDebug("meta_record has an item=$item and mdb=$mdb"); 671 672 my $record; 673 674 my $mkey = $view ? "${view}::$item" : $item; 675 676 if( ref ($mdb) eq 'HASH') { 677 $record = $mdb; 678 } 679 else { 680 $record = $mdb->row_hash($mkey); 681#::logDebug("used mkey=$mkey to select record=$record"); 682 } 683 684 $record ||= $mdb->row_hash($item) if $view and $mdb; 685#::logDebug("meta_record record=$record"); 686 687 return undef if ! $record; 688 689 # Get additional settings from extended field, which is a serialized 690 # hash 691 my $hash; 692 if(! $record->{extended}) { 693 return undef if $extended_only; 694 } 695 else { 696 ## From Vend::Util 697 $hash = get_option_hash($record->{extended}); 698 $record = {} if $extended_only; 699 if(ref $hash eq 'HASH') { 700 @$record{keys %$hash} = values %$hash; 701 } 702 else { 703 undef $hash; 704 return undef if $extended_only; 705 } 706 } 707 708 # Allow view settings to be placed in the extended area 709 if($view and $hash and $hash->{view}) { 710 my $view_hash = $record->{view}{$view}; 711 ref $view_hash 712 and @$record{keys %$view_hash} = values %$view_hash; 713 } 714 715 # Allow overlay of certain settings 716 if($overlay and $record->{overlay}) { 717 my $ol_hash = $record->{overlay}{$overlay}; 718 Vend::Util::copyref($ol_hash, $record) if $ol_hash; 719 } 720#::logDebug("return meta_record=" . ::uneval($record) ); 721 return $record; 722} 723 724my $base_entry_value; 725 726sub display { 727 my ($table,$column,$key,$opt) = @_; 728 729 if( ref($opt) ne 'HASH' ) { 730 $opt = get_option_hash($opt); 731 } 732 733 my $template = $opt->{type} eq 'hidden' ? '' : $opt->{template}; 734 735 if($opt->{override}) { 736 $opt->{value} = defined $opt->{default} ? $opt->{default} : ''; 737 } 738 739 if(! defined $opt->{value} and $table and $column and length($key)) { 740 $opt->{value} = tag_data($table, $column, $key); 741 $opt->{already_got_data} = 1; 742 } 743 744 my $mtab; 745 my $record; 746 747 my $no_meta = $opt->{ui_no_meta_display}; 748 749 METALOOK: { 750 ## No meta display wanted 751 last METALOOK if $no_meta; 752 ## No meta display possible 753 $table and $column or $opt->{meta} 754 or last METALOOK; 755 756 ## We get a metarecord directly, though why it would be here 757 ## and not in options I don't know 758 if($opt->{meta} and ref($opt->{meta}) eq 'HASH') { 759 $record = $opt->{meta}; 760 last METALOOK; 761 } 762 763 $mtab = $opt->{meta_table} || $::Variable->{UI_META_TABLE} || 'mv_metadata' 764 or last METALOOK; 765 my $meta = Vend::Data::database_exists_ref($mtab) 766 or do { 767 ::logError("non-existent meta table: %s", $mtab); 768 undef $mtab; 769 last METALOOK; 770 }; 771 772 my $view = $opt->{view} || $opt->{arbitrary}; 773 774 ## This is intended to trigger on the first access 775 if($table eq $mtab and $column eq $meta->config('KEY')) { 776 if($view and $opt->{value} !~ /::.+::/) { 777 $base_entry_value = ($opt->{value} =~ /^([^:]+)::(\w+)$/) 778 ? $1 779 : $opt->{value}; 780 } 781 else { 782 $base_entry_value = $opt->{value} =~ /(\w+)::/ 783 ? $1 784 : $opt->{value}; 785 } 786 } 787 788 my (@tries) = "${table}::$column"; 789 unshift @tries, "${table}::${column}::$key" 790 if length($key) and $opt->{specific}; 791 792 my $sess = $Vend::Session->{mv_metadata} || {}; 793 794 push @tries, { type => $opt->{type} } 795 if $opt->{type} || $opt->{label}; 796 797 for my $metakey (@tries) { 798 ## In case we were passed a meta record 799 last if $record = $sess->{$metakey} and ref $record; 800 $record = meta_record($metakey, $view, $meta) 801 and last; 802 } 803 } 804 805 my $w; 806 807 METAMAKE: { 808 last METAMAKE if $no_meta; 809 if( ! $record ) { 810 $record = { %$opt }; 811 } 812 else { 813 ## Here we allow override with the display tag, even with views and 814 ## extended 815 my @override = qw/ 816 append 817 attribute 818 db 819 callback_prescript 820 callback_postscript 821 class 822 default 823 extra 824 disabled 825 field 826 form 827 form_name 828 filter 829 height 830 help 831 help_url 832 label 833 js_check 834 lookup 835 lookup_exclude 836 lookup_query 837 maxlength 838 name 839 options 840 outboard 841 passed 842 pre_filter 843 prepend 844 table 845 type 846 type_empty 847 width 848 /; 849 for(@override) { 850 delete $record->{$_} if ! length($record->{$_}); 851 next unless defined $opt->{$_}; 852 $record->{$_} = $opt->{$_}; 853 } 854 } 855 856 if($record->{type_empty} and length($opt->{value}) == 0) { 857 $record->{type} = $record->{type_empty}; 858 } 859 else { 860 $record->{type} ||= $opt->{default_widget}; 861 } 862 863 $record->{name} ||= $column; 864#::logDebug("record now=" . ::uneval($record)); 865 866 if($record->{options} and $record->{options} =~ /^[\w:,]+$/) { 867#::logDebug("checking options"); 868 PASS: { 869 my $passed = $record->{options}; 870 871 if($passed eq 'tables') { 872 my @tables = $Tag->list_databases(); 873 $record->{passed} = join (',', "=--none--", @tables); 874 } 875 elsif($passed =~ /^(?:filters|\s*codedef:+(\w+)(:+(\w+))?\s*)$/i) { 876 my $tag = $1 || 'filters'; 877 my $mod = $3; 878 $record->{passed} = Vend::Util::codedef_options($tag, $mod); 879 } 880 elsif($passed =~ /^columns(::(\w*))?\s*$/) { 881 my $total = $1; 882 my $tname = $2 || $record->{db} || $table; 883 if ($total eq '::' and $base_entry_value) { 884 $tname = $base_entry_value; 885 } 886 $record->{passed} = join ",", 887 "=--none--", 888 $Tag->db_columns($tname), 889 ; 890 } 891 elsif($passed =~ /^keys(::(\w+))?\s*$/) { 892 my $tname = $2 || $record->{db} || $table; 893 $record->{passed} = join ",", 894 "=--none--", 895 $Tag->list_keys($tname), 896 ; 897 } 898 } 899 } 900 901#::logDebug("checking for custom widget"); 902 if ($record->{type} =~ s/^custom\s+//s) { 903 my $wid = lc $record->{type}; 904 $wid =~ tr/-/_/; 905 $record->{attribute} ||= $column; 906 $record->{table} ||= $mtab; 907 $record->{rows} ||= $record->{height}; 908 $record->{cols} ||= $record->{width}; 909 $record->{field} ||= 'options'; 910 $record->{name} ||= $column; 911 eval { 912 $w = $Tag->$wid($record->{name}, $opt->{value}, $record, $opt); 913 }; 914 if($@) { 915 ::logError("error using custom widget %s: %s", $wid, $@); 916 } 917 last METAMAKE; 918 } 919 920 $opt->{restrict_allow} ||= $record->{restrict_allow}; 921#::logDebug("formatting prepend/append/lookup_query name=$opt->{name} restrict_allow=$opt->{restrict_allow}"); 922 for(qw/append prepend lookup_query/) { 923 next unless $record->{$_}; 924 if($opt->{restrict_allow}) { 925 $record->{$_} = $Tag->restrict({ 926 log => 'none', 927 enable => $opt->{restrict_allow}, 928 disable => $opt->{restrict_deny}, 929 body => $record->{$_}, 930 }); 931 } 932 else { 933 $record->{$_} = expand_values($record->{$_}); 934 } 935 $record->{$_} = Vend::Util::resolve_links($record->{$_}); 936 $record->{$_} =~ s/_UI_VALUE_/$opt->{value}/g; 937 $record->{$_} =~ /_UI_URL_VALUE_/ 938 and do { 939 my $tmp = $opt->{value}; 940 $tmp =~ s/(\W)/sprintf '%%%02x', ord($1)/eg; 941 $record->{$_} =~ s/_UI_URL_VALUE_/$tmp/g; 942 }; 943 $record->{$_} =~ s/_UI_TABLE_/$table/g; 944 $record->{$_} =~ s/_UI_COLUMN_/$column/g; 945 $record->{$_} =~ s/_UI_KEY_/$key/g; 946 } 947 948 if($opt->{opts}) { 949 my $r = get_option_hash(delete $opt->{opts}); 950 for my $k (keys %$r) { 951 $record->{$k} = $r->{$k}; 952 } 953 } 954 955 956#::logDebug("overriding defaults"); 957#::logDebug("passed=$record->{passed}") if $record->{debug}; 958 my %things = ( 959 attribute => $column, 960 cols => $opt->{cols} || $record->{width}, 961 passed => $record->{options}, 962 rows => $opt->{rows} || $record->{height}, 963 value => $opt->{value}, 964 applylocale => $opt->{applylocale}, 965 ); 966 967 while( my ($k, $v) = each %things) { 968 next if length $record->{$k}; 969 next unless defined $v; 970 $record->{$k} = $v; 971 } 972 973#::logDebug("calling Vend::Form with record=" . ::uneval($record)); 974 if($record->{save_defaults}) { 975 my $sd = $Vend::Session->{meta_defaults} ||= {}; 976 $sd = $sd->{"${table}::$column"} ||= {}; 977 while (my ($k,$v) = each %$record) { 978 next if ref($v) eq 'CODE'; 979 $sd->{$k} = $v; 980 } 981 } 982 983 $w = Vend::Form::display($record); 984 if($record->{filter}) { 985 $w .= qq{<input type="hidden" name="ui_filter:$record->{name}" value="}; 986 $w .= $record->{filter}; 987 $w .= '">'; 988 } 989 } 990 991 if(! defined $w) { 992 my $text = $opt->{value}; 993 my $iname = $opt->{name} || $column; 994 995 # Count lines for textarea 996 my $count; 997 $count = $text =~ s/(\r\n|\r|\n)/$1/g; 998 999 HTML::Entities::encode($text, $ESCAPE_CHARS::std); 1000 my $size; 1001 if ($count) { 1002 $count++; 1003 $count = 20 if $count > 20; 1004 $w = <<EOF; 1005 <textarea name="$iname" cols="60" rows="$count">$text</textarea> 1006EOF 1007 } 1008 elsif ($text =~ /^\d+$/) { 1009 my $cur = length($text); 1010 $size = $cur > 8 ? $cur + 1 : 8; 1011 } 1012 else { 1013 $size = 60; 1014 } 1015 $w = <<EOF; 1016 <input name="$iname" size="$size" value="$text"> 1017EOF 1018 } 1019 1020 my $array_return = wantarray; 1021 1022#::logDebug("widget=$w"); 1023 1024 # don't output label if widget is hidden form variable only 1025 # and not an array type 1026 undef $template if $w =~ /^\s*<input\s[^>]*type\s*=\W*hidden\b[^>]*>\s*$/i; 1027 1028 return $w unless $template || $opt->{return_hash} || $array_return; 1029 1030 if($template and $template !~ /\s/) { 1031 $template = <<EOF; 1032<tr> 1033<td> 1034 <b>\$LABEL\$</b> 1035</td> 1036<td valign="top"> 1037 <table cellspacing="0" cellmargin="0"><tr><td>\$WIDGET\$</td><td>\$HELP\${HELP_URL}<br$Vend::Xtrailer><a href="\$HELP_URL\$">help</a>{/HELP_URL}</td></tr></table> 1038</td> 1039</tr> 1040EOF 1041 } 1042 1043 $record->{label} ||= $column; 1044 1045 my %sub = ( 1046 WIDGET => $w, 1047 HELP => $opt->{applylocale} 1048 ? errmsg($record->{help}) 1049 : $record->{help}, 1050 META_URL => $opt->{meta_url}, 1051 HELP_URL => $record->{help_url}, 1052 LABEL => $opt->{applylocale} 1053 ? errmsg($record->{label}) 1054 : $record->{label}, 1055 ); 1056#::logDebug("passed meta_url=$opt->{meta_url}"); 1057 $sub{HELP_EITHER} = $sub{HELP} || $sub{HELP_URL}; 1058 1059 if($opt->{return_hash}) { 1060 $sub{OPT} = $opt; 1061 $sub{RECORD} = $record; 1062 return \%sub; 1063 } 1064 elsif($array_return) { 1065 return ($w, $sub{LABEL}, $sub{HELP}, $record->{help_url}); 1066 } 1067 else { 1068 # Strip the {TAG} {/TAG} pairs if nothing there 1069 $template =~ s#{([A-Z_]+)}(.*?){/\1}#$sub{$1} ? $2: '' #ges; 1070 # Insert the TAG 1071 $sub{HELP_URL} ||= 'javascript:void(0)'; 1072 $template =~ s/\$([A-Z_]+)\$/$sub{$1}/g; 1073#::logDebug("substituted template is: $template"); 1074 return $template; 1075 } 1076} 1077 1078sub tabbed_display { 1079 my ($tit, $cont, $opt) = @_; 1080 1081 $opt ||= {}; 1082 1083 my @colors; 1084 $opt->{tab_bgcolor_template} ||= '#xxxxxx'; 1085 $opt->{tab_height} ||= '20'; $opt->{tab_width} ||= '120'; 1086 $opt->{panel_id} ||= 'mvpan'; 1087 $opt->{tab_horiz_offset} ||= '10'; 1088 $opt->{tab_vert_offset} ||= '8'; 1089 my $width_height; 1090 $opt->{tab_style} ||= q{ 1091 text-align:center; 1092 font-family: sans-serif; 1093 line-height:150%; 1094 font-size: smaller; 1095 border:2px; 1096 border-color:#999999; 1097 border-style:outset; 1098 border-bottom-style:none; 1099 }; 1100 if($opt->{ui_style}) { 1101 $opt->{panel_style} ||= q{ 1102 padding: 0; 1103 }; 1104 $width_height = ''; 1105 } 1106 else { 1107 $opt->{panel_style} ||= q{ 1108 font-family: sans-serif; 1109 font-size: smaller; 1110 padding: 0; 1111 border: 2px; 1112 border-color:#999999; 1113 border-style:outset; 1114 }; 1115 $opt->{panel_height} ||= '600'; 1116 $opt->{panel_width} ||= '800'; 1117 $width_height = <<EOF; 1118 width: $opt->{panel_width}px; 1119 height: $opt->{panel_height}px; 1120EOF 1121 } 1122 1123 $opt->{panel_shade} ||= 'e'; 1124 1125 my @chars = reverse(0 .. 9, 'a' .. $opt->{panel_shade}); 1126 my $id = $opt->{panel_id}; 1127 my $vpf = $id . '_'; 1128 my $num_panels = scalar(@$cont); 1129 my $tabs_per_row = int( $opt->{panel_width} / $opt->{tab_width}) || 1; 1130 my $num_rows = POSIX::ceil( $num_panels / $tabs_per_row); 1131 my $width = $opt->{panel_width}; 1132 my $height = $opt->{tab_height} * $num_rows + $opt->{panel_height}; 1133 my $panel_y; 1134 my $int1; 1135 my $int2; 1136 if($opt->{ui_style}) { 1137 $panel_y = 2; 1138 $int1 = $int2 = 0; 1139 } 1140 else { 1141 $panel_y = 1142 $num_rows 1143 * ($opt->{tab_height} - $opt->{tab_vert_offset}) 1144 + $opt->{tab_vert_offset}; 1145 $int1 = $panel_y - 2; 1146 $int2 = $opt->{tab_height} * $num_rows; 1147 } 1148 for(my $i = 0; $i < $num_panels; $i++) { 1149 my $c = $opt->{tab_bgcolor_template} || '#xxxxxx'; 1150 $c =~ s/x/$chars[$i] || $opt->{panel_shade}/eg; 1151 $colors[$i] = $c; 1152 } 1153 my $cArray = qq{var ${vpf}colors = ['} . join("','", @colors) . qq{'];}; 1154#::logDebug("num rows=$num_rows"); 1155 my $out = <<EOF; 1156<script language="JavaScript"> 1157<!-- 1158var ${vpf}panelID = "$id" 1159var ${vpf}numDiv = $num_panels; 1160var ${vpf}numRows = $num_rows; 1161var ${vpf}tabsPerRow = $tabs_per_row; 1162var ${vpf}numLocations = ${vpf}numRows * ${vpf}tabsPerRow 1163var ${vpf}tabWidth = $opt->{tab_width} 1164var ${vpf}tabHeight = $opt->{tab_height} 1165var ${vpf}vOffset = $opt->{tab_vert_offset}; 1166var ${vpf}hOffset = $opt->{tab_horiz_offset}; 1167$cArray 1168 1169var ${vpf}uptabs = new Array; 1170var ${vpf}dntabs = new Array; 1171var ${vpf}divLocation = new Array(${vpf}numLocations) 1172var ${vpf}newLocation = new Array(${vpf}numLocations) 1173for(var i=0; i<${vpf}numLocations; ++i) { 1174 ${vpf}divLocation[i] = i 1175 ${vpf}newLocation[i] = i 1176} 1177 1178function ${vpf}getDiv(s,i) { 1179 var div 1180 if (document.layers) { 1181 div = document.layers[${vpf}panelID].layers[panelID+s+i] 1182 } else if (document.all && !document.getElementById) { 1183 div = document.all[${vpf}panelID+s+i] 1184 } else { 1185 div = document.getElementById(${vpf}panelID+s+i) 1186 } 1187 return div 1188} 1189 1190function ${vpf}setZIndex(div, zIndex) { 1191 if (document.layers) div.style = div; 1192 div.style.zIndex = zIndex 1193} 1194 1195function ${vpf}updatePosition(div, newPos) { 1196 ${vpf}newClip=${vpf}tabHeight*(Math.floor(newPos/${vpf}tabsPerRow)+1) 1197 if (document.layers) { 1198 div.style=div; 1199 div.clip.bottom=${vpf}newClip; // clip off bottom 1200 } else { 1201 div.style.clip="rect(0 auto "+${vpf}newClip+" 0)" 1202 } 1203 div.style.top = (${vpf}numRows-(Math.floor(newPos/${vpf}tabsPerRow) + 1)) * (${vpf}tabHeight-${vpf}vOffset) 1204 div.style.left = (newPos % ${vpf}tabsPerRow) * ${vpf}tabWidth + (${vpf}hOffset * (Math.floor(newPos / ${vpf}tabsPerRow))) 1205} 1206 1207function ${vpf}tripTab(n) { 1208 // n is the ID of the division that was clicked 1209 // firstTab is the location of the first tab in the selected row 1210 var el; 1211 for(var i = 0; i < ${vpf}dntabs.length; i++) { 1212 el = document.getElementById('${vpf}td' + i); 1213 if(el != undefined) { 1214 el.innerHTML = ${vpf}dntabs[ i ]; 1215 el.style.backgroundColor = '#B4B0AA'; 1216 } 1217 } 1218 el = document.getElementById('${vpf}td' + n); 1219 el.innerHTML = ${vpf}uptabs[ n ]; 1220 el.style.backgroundColor = '#D4D0C8'; 1221 // Set tab positions & zIndex 1222 // Update location 1223 var j = 1; 1224 for(var i=0; i<${vpf}numDiv; ++i) { 1225 var loc = ${vpf}newLocation[i] 1226 var div = ${vpf}getDiv("panel",i) 1227 if(i == n) { 1228 ${vpf}setZIndex(div, ${vpf}numLocations +1); 1229 div.style.display = 'block'; 1230 div.style.backgroundColor = ${vpf}colors[0]; 1231 } 1232 else { 1233 ${vpf}setZIndex(div, ${vpf}numLocations - loc) 1234 div.style.display = 'none'; 1235 div.style.backgroundColor = ${vpf}colors[j++]; 1236 } 1237 ${vpf}divLocation[i] = loc 1238 } 1239} 1240 1241function ${vpf}selectTab(n) { 1242 // n is the ID of the division that was clicked 1243 // firstTab is the location of the first tab in the selected row 1244 var firstTab = Math.floor(${vpf}divLocation[n] / ${vpf}tabsPerRow) * ${vpf}tabsPerRow 1245 // newLoc is its new location 1246 for(var i=0; i<${vpf}numDiv; ++i) { 1247 // loc is the current location of the tab 1248 var loc = ${vpf}divLocation[i] 1249 // If in the selected row 1250 if(loc >= firstTab && loc < (firstTab + ${vpf}tabsPerRow)) ${vpf}newLocation[i] = (loc - firstTab) 1251 else if(loc < ${vpf}tabsPerRow) ${vpf}newLocation[i] = firstTab+(loc % ${vpf}tabsPerRow) 1252 else ${vpf}newLocation[i] = loc 1253 } 1254 // Set tab positions & zIndex 1255 // Update location 1256 var j = 1; 1257 for(var i=0; i<${vpf}numDiv; ++i) { 1258 var loc = ${vpf}newLocation[i] 1259 var div = ${vpf}getDiv("panel",i) 1260 var tdiv = ${vpf}getDiv("tab",i) 1261 if(i == n) { 1262 ${vpf}setZIndex(div, ${vpf}numLocations +1); 1263 div.style.display = 'block'; 1264 tdiv.style.backgroundColor = ${vpf}colors[0]; 1265 div.style.backgroundColor = ${vpf}colors[0]; 1266 } 1267 else { 1268 ${vpf}setZIndex(div, ${vpf}numLocations - loc) 1269 div.style.display = 'none'; 1270 tdiv.style.backgroundColor = ${vpf}colors[j]; 1271 div.style.backgroundColor = ${vpf}colors[j++]; 1272 } 1273 ${vpf}divLocation[i] = loc 1274 ${vpf}updatePosition(tdiv, loc) 1275 if(i == n) ${vpf}setZIndex(tdiv, ${vpf}numLocations +1) 1276 else ${vpf}setZIndex(tdiv,${vpf}numLocations - loc) 1277 } 1278} 1279 1280//--> 1281</script> 1282<style type="text/css"> 1283<!-- 1284.${id}tab { 1285 font-weight: bold; 1286 width:$opt->{tab_width}px; 1287 margin:0px; 1288 height: ${int2}px; 1289 position:relative; 1290 $opt->{tab_style} 1291 } 1292 1293.${id}panel { 1294 position:relative; 1295 width: $opt->{panel_width}px; 1296 height: $opt->{panel_height}px; 1297 left:0px; 1298 top:${int1}px; 1299 margin:0px; 1300 $opt->{panel_style} 1301 } 1302--> 1303</style> 1304EOF 1305 my $s1 = ''; 1306 my $s2 = ''; 1307 my $ibase = $Tag->image({ 1308 ui => $Vend::admin, 1309 dir_only => 1, 1310 secure => $Vend::admin && $::Variable->{UI_SECURE}, 1311 }); 1312 $opt->{clear_image} ||= 'bg.gif'; 1313 my $clear = "$ibase/$opt->{clear_image}"; 1314 my @dntabs; 1315 my @uptabs; 1316 for(my $i = 0; $i < $num_panels; $i++) { 1317 my $zi = $num_panels - $i; 1318 my $pnum = $i + 1; 1319 my $left = (($i % $tabs_per_row) 1320 * $opt->{tab_width} 1321 + ($opt->{tab_horiz_offset} 1322 * (int($i / $tabs_per_row)))); 1323 my $top = ( $num_rows - (int($i / $tabs_per_row) + 1)) 1324 - ($opt->{tab_height} - $opt->{tab_vert_offset}); 1325 my $cliprect = $opt->{tab_height} * (int($i / $tabs_per_row) + 1); 1326 $s1 .= <<EOF; 1327<div id="${id}panel$i" 1328 class="${id}panel" 1329 style=" 1330 background-color: $colors[$i]; 1331 z-index:$zi 1332 "> 1333$opt->{panel_prepend} 1334$cont->[$i] 1335$opt->{panel_append} 1336</div> 1337EOF 1338 if($opt->{ui_style}) { 1339 $s2 .= <<EOF; 1340<td class="subtabdown" id="${vpf}td$i"> 1341EOF 1342 1343 $dntabs[$i] = <<EOF; 1344 <table width="100%" border="0" cellspacing="0" cellpadding="0"> 1345 <tr> 1346 <td class="subtabdownleft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td> 1347 <td nowrap class="subtabdownfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td> 1348 <td class="subtabdownright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td> 1349 </tr> 1350 <tr> 1351 <td colspan="3" class="darkshade"><img src="$clear" height="1"></td> 1352 </tr> 1353 <tr> 1354 <td colspan="3" class="lightshade"><img src="$clear" height="1"></td> 1355 </tr> 1356 </table> 1357EOF 1358 1359 $s2 .= $dntabs[$i]; 1360 1361 $uptabs[$i] = <<EOF; 1362 <table width="100%" border="0" cellspacing="0" cellpadding="0"> 1363 <tr> 1364 <td class="subtableft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td> 1365 <td nowrap class="subtabfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td> 1366 <td class="subtabright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td> 1367 </tr> 1368 <tr> 1369 <td colspan="3" class="subtabfilllwr"><img src="$clear" height="1"></td> 1370 </tr> 1371 </table> 1372EOF 1373 $s2 .= "</td>\n"; 1374 1375 } 1376 else { 1377 $s1 .= <<EOF; 1378<div 1379 onclick="${vpf}selectTab($i)" 1380 id="${id}tab$i" 1381 class="${id}tab" 1382 style=" 1383 position: absolute; 1384 background-color: $colors[$i]; 1385 cursor: pointer; 1386 left: ${left}px; 1387 top: ${top}px; 1388 z-index:$zi; 1389 clip:rect(0 auto $cliprect 0); 1390 "> 1391$tit->[$i] 1392</div> 1393EOF 1394 } 1395 } 1396 1397 my $start_index = $opt->{start_at_index} || 0; 1398 $start_index += 0; 1399 if($s2) { 1400 $Tag->output_to('third_tabs', { name => 'third_tabs' }, $s2); 1401 } 1402 $out .= <<EOF; 1403<div style=" 1404 position: relative; 1405 left: 0; top: 0; width: 100%; height: 100%; 1406 z-index: 0; 1407 "> 1408$s1 1409EOF 1410 if($s2) { 1411 $out .= <<EOF; 1412<script> 1413EOF 1414 for(my $i = 0; $i < @dntabs; $i++) { 1415 $out .= "${vpf}uptabs[ $i ] = "; 1416 $out .= $Tag->jsq($uptabs[$i]); 1417 $out .= ";\n"; 1418 $out .= "${vpf}dntabs[ $i ] = "; 1419 $out .= $Tag->jsq($dntabs[$i]); 1420 $out .= ";\n"; 1421 } 1422 $out .= <<EOF; 1423 ${vpf}tripTab($start_index); 1424</script> 1425EOF 1426 } 1427 else { 1428 $out .= <<EOF; 1429<script> 1430 ${vpf}selectTab($start_index); 1431</script> 1432EOF 1433 } 1434 1435 $out .= <<EOF; 1436</div> 1437EOF 1438 1439} 1440 1441my $tcount_all; 1442my %alias; 1443my %exclude; 1444my %outhash; 1445my @titles; 1446my @controls; 1447my $ctl_index = 0; 1448my @out; 1449 1450sub ttag { 1451 return 'TABLE_STD' . ++$tcount_all; 1452} 1453 1454sub add_exclude { 1455 my ($tag, $string) = @_; 1456#::logDebug("calling add_exclude tag='$tag' string='$string'"); 1457 return unless $string =~ /\S/; 1458 $exclude{$tag} ||= ' '; 1459 $exclude{$tag} .= "$string "; 1460} 1461 1462sub col_chunk { 1463 my $value = pop @_; 1464 my $tag = shift @_; 1465 my $exclude = shift @_; 1466 my @others = @_; 1467 1468 $tag = "COLUMN_$tag"; 1469 1470#::logDebug("$tag content length=" . length($value)); 1471 1472 if(exists $outhash{$tag}) { 1473 my $col = $tag; 1474 $col =~ s/^COLUMN_//; 1475 my $msg = errmsg("Column '%s' defined twice, skipping second.", $col); 1476 Vend::Tags->warnings($msg); 1477 return; 1478 } 1479 1480 $outhash{$tag} = $value; 1481 1482 if(@others) { 1483 $alias{$tag} ||= []; 1484 push @{$alias{$tag}}, @others; 1485 } 1486 1487 my $ctl = $controls[$ctl_index] ||= []; 1488 add_exclude($tag, $exclude) if $exclude; 1489 1490 return unless length($value); 1491 1492 push @$ctl, $tag; 1493 return; 1494} 1495 1496sub chunk_alias { 1497 my $tag = shift; 1498 $alias{$tag} ||= []; 1499 push @{$alias{$tag}}, @_; 1500 return; 1501} 1502 1503sub chunk { 1504 my $value = pop @_; 1505 my $tag = shift @_; 1506 my $exclude = shift @_; 1507 my @others = @_; 1508 1509 die "duplicate tag settor $tag" if exists $outhash{$tag}; 1510 $outhash{$tag} = $value; 1511 1512#::logDebug("$tag exclude=$exclude, content length=" . length($value)); 1513 1514 if(@others) { 1515 $alias{$tag} ||= []; 1516 push @{$alias{$tag}}, @others; 1517 } 1518 1519 add_exclude($tag, $exclude) if $exclude =~ /\S/; 1520 1521 return unless length($value); 1522 push @out, $tag; 1523} 1524 1525sub resolve_exclude { 1526 my $exc = shift; 1527 while(my ($k, $v) = each %exclude) { 1528 my %seen; 1529 my @things = grep /\S/ && ! $seen{$_}++, split /\s+/, $v; 1530#::logDebug("examining $k for $v"); 1531 for my $thing (@things) { 1532 if($thing =~ s/^[^A-Z]//) { 1533#::logDebug("examining $v for $thing!=$exc->{$thing}"); 1534 $outhash{$k} = '' unless $exc->{$thing}; 1535 } 1536 else { 1537#::logDebug("examining $v for $thing=$exc->{$thing}"); 1538 $outhash{$k} = '' if $exc->{$thing}; 1539 } 1540 } 1541 } 1542} 1543 1544sub editor_init { 1545 undef $base_entry_value; 1546 1547 ## Why? 1548 Vend::Interpolate::init_calc() if ! $Vend::Calc_initialized; 1549 @out = (); 1550 @controls = (); 1551 @titles = (); 1552 %outhash = (); 1553 %exclude = (); 1554 %alias = (); 1555 $tcount_all = 0; 1556 $ctl_index = 0; 1557} 1558 1559my %o_default_length = ( 1560 border_cell_class => 'cborder', 1561 widget_cell_class => 'cwidget', 1562 label_cell_class => 'clabel', 1563 data_cell_class => 'cdata', 1564 help_cell_class => 'chelp', 1565 break_cell_class_first => 'cbreakfirst', 1566 break_cell_class => 'cbreak', 1567 spacer_row_class => 'rspacer', 1568 break_row_class => 'rbreak', 1569 title_row_class => 'rmarq', 1570 data_row_class => 'rnorm', 1571 ok_button_style => 'font-weight: bold; width: 40px; text-align: center', 1572); 1573 1574my %o_default_var = (qw/ 1575 color_fail UI_CONTRAST 1576 color_success UI_C_SUCCESS 1577/); 1578 1579my %o_default_defined = ( 1580 mv_update_empty => 1, 1581 restrict_allow => 'page area var', 1582); 1583 1584my %o_default = ( 1585 action => 'set', 1586 wizard_next => 'return', 1587 help_anchor => 'help', 1588 wizard_cancel => 'back', 1589 across => 1, 1590 color_success => '#00FF00', 1591 color_fail => '#FF0000', 1592 spacer_height => 1, 1593 border_height => 1, 1594 clear_image => 'bg.gif', 1595 table_width => '100%', 1596); 1597 1598# Build maps for ui_te_* option pass 1599my @cgi_opts = qw/ 1600 1601 append 1602 check 1603 database 1604 default 1605 disabled 1606 extra 1607 field 1608 filter 1609 height 1610 help 1611 help_url 1612 label 1613 lookup 1614 options 1615 outboard 1616 override 1617 passed 1618 pre_filter 1619 prepend 1620 template 1621 widget 1622 width 1623 1624/; 1625 1626my @hmap; 1627 1628for(@cgi_opts) { 1629 push @hmap, [ qr/ui_te_$_:/, $_ ]; 1630} 1631 1632my %ignore_cgi = qw/ 1633 item_id 1 1634 item_id_left 1 1635 mv_pc 1 1636 mv_action 1 1637 mv_todo 1 1638 mv_ui 1 1639 mv_data_table 1 1640 mv_session_id 1 1641 mv_nextpage 1 1642 ui_sequence_edit 1 1643 /; 1644sub save_cgi { 1645 my $ref = {}; 1646 my @k; 1647 if($CGI::values{save_cgi}) { 1648 @k = split /\0/, $CGI::values{save_cgi}; 1649 } 1650 else { 1651 @k = grep ! $ignore_cgi{$_}, keys %CGI::values; 1652 } 1653 1654 # Can be an array because of produce_hidden 1655 $ref->{save_cgi} = \@k; 1656 1657 for(@k) { 1658 $ref->{$_} = $CGI::values{$_}; 1659 } 1660 return $ref; 1661} 1662 1663sub produce_hidden { 1664 my ($key, $val) = @_; 1665 return unless length $val; 1666 my @p; # pieces of var 1667 my @o; # output 1668 if(ref($val) eq 'ARRAY') { 1669 @p = @$val; 1670 } 1671 else { 1672 @p = split /\0/, $val; 1673 } 1674 for(@p) { 1675 s/"/"/g; 1676 push @o, qq{<input type="hidden" name="$key" value="$_">\n}; 1677 } 1678 return join "", @o; 1679} 1680 1681sub resolve_options { 1682 my ($opt, $CGI, $data) = @_; 1683 1684 # This may be passed by the caller, but is normally from the form 1685 # or URL 1686 $CGI ||= \%CGI::values; 1687 1688 my $table = $opt->{mv_data_table}; 1689 my $key = $opt->{item_id}; 1690 1691 $table = $CGI->{mv_data_table} 1692 if ! $table and $opt->{cgi} and $CGI->{mv_data_table}; 1693 1694 $opt->{table} = $opt->{mv_data_table} = $table; 1695 1696 # First we see if something has created a big options munge 1697 # for us 1698 if($opt->{all_opts}) { 1699#::logDebug("all_opts being brought in...=" . ::uneval($opt->{all_opts})); 1700 if(ref($opt->{all_opts}) eq 'HASH') { 1701#::logDebug("all_opts being brought in..."); 1702 my $o = $opt->{all_opts}; 1703 for (keys %$o ) { 1704 $opt->{$_} = $o->{$_}; 1705 } 1706 } 1707 else { 1708 my $o = meta_record($opt->{all_opts}); 1709#::logDebug("all_opts being brought in text, o=$o"); 1710 if($o) { 1711 for (keys %$o ) { 1712 $opt->{$_} = $o->{$_}; 1713 } 1714 } 1715 else { 1716 logError("%s: improper option %s, must be %s, was %s.", 1717 'table_editor', 1718 'all_opts', 1719 'hash', 1720 ref $opt->{all_opts}, 1721 ); 1722 } 1723 } 1724#::logDebug("options now=" . ::uneval($opt)); 1725 } 1726 1727 my @mapdirect = qw/ 1728 back_button_class 1729 back_button_style 1730 border_cell_class 1731 border_height 1732 bottom_buttons 1733 break_cell_class 1734 break_cell_style 1735 break_row_class 1736 break_row_style 1737 button_delete 1738 cancel_button_class 1739 cancel_button_style 1740 clear_image 1741 data_cell_class 1742 data_cell_style 1743 data_row_class 1744 data_row_style 1745 default_widget 1746 delete_button_class 1747 delete_button_style 1748 display_type 1749 file_upload 1750 help_anchor 1751 help_cell_class 1752 help_cell_style 1753 image_meta 1754 include_before 1755 include_form 1756 include_form_expand 1757 include_form_interpolate 1758 intro_text 1759 label_cell_class 1760 label_cell_style 1761 left_width 1762 link_auto_number 1763 link_before 1764 link_blank_auto 1765 link_extra 1766 link_fields 1767 link_key 1768 link_label 1769 link_no_blank 1770 link_row_qual 1771 link_rows_blank 1772 link_sort 1773 link_table 1774 link_template 1775 link_view 1776 mv_auto_export 1777 mv_blob_field 1778 mv_blob_label 1779 mv_blob_nick 1780 mv_blob_pointer 1781 mv_blob_title 1782 mv_data_decode 1783 mv_data_table 1784 mv_update_empty 1785 next_button_class 1786 next_button_style 1787 no_meta 1788 nodelete 1789 ok_button_class 1790 ok_button_style 1791 output_map 1792 panel_class 1793 panel_height 1794 panel_id 1795 panel_shade 1796 panel_style 1797 panel_width 1798 reset_button_class 1799 reset_button_style 1800 restrict_allow 1801 spacer_height 1802 spacer_row_class 1803 spacer_row_style 1804 start_at 1805 tab_bgcolor_template 1806 tab_cellpadding 1807 tab_cellspacing 1808 tab_class 1809 tab_height 1810 tab_horiz_offset 1811 tab_style 1812 tab_vert_offset 1813 tab_width 1814 tabbed 1815 table_height 1816 table_width 1817 title_row_class 1818 title_row_style 1819 top_buttons 1820 ui_break_before 1821 ui_break_before_label 1822 ui_data_fields 1823 ui_data_fields_all 1824 ui_data_key_name 1825 ui_delete_box 1826 ui_display_only 1827 ui_hide_key 1828 ui_meta_specific 1829 ui_meta_view 1830 ui_new_item 1831 ui_nextpage 1832 ui_no_meta_display 1833 ui_profile 1834 view_from 1835 widget_cell_class 1836 widget_cell_style 1837 widget_class 1838 xhtml 1839 /; 1840 1841 if($opt->{cgi}) { 1842 for(@mapdirect) { 1843 next if ! defined $CGI->{$_}; 1844 $opt->{$_} = $CGI->{$_}; 1845 } 1846 my @cgi = keys %{$CGI}; 1847 foreach my $row (@hmap) { 1848 my @keys = grep $_ =~ $row->[0], @cgi; 1849 for(@keys) { 1850 /^ui_\w+:(\S+)/ 1851 and $opt->{$row->[1]}{$1} = $CGI->{$_}; 1852 } 1853 } 1854 } 1855 1856#::logDebug("no_meta_display=$opt->{ui_no_meta_display}"); 1857 my $tmeta; 1858 if($opt->{no_table_meta} || $opt->{ui_no_meta_display}) { 1859 $tmeta = {}; 1860 } 1861 else { 1862 $tmeta = meta_record($table, $opt->{ui_meta_view}) || {}; 1863 } 1864 1865 $opt->{view_from} ||= $tmeta->{view_from}; 1866 1867 my $baseopt; 1868 if($opt->{no_base_meta} || $opt->{ui_no_meta_display}) { 1869 $baseopt = {}; 1870 } 1871 else { 1872 $baseopt = meta_record('table-editor') || {}; 1873 delete $baseopt->{extended}; 1874 } 1875 1876 if( ! $opt->{ui_meta_view} 1877 and $opt->{view_from} 1878 and $data 1879 and ! $opt->{ui_no_meta_display} 1880 and $opt->{ui_meta_view} = $data->{$opt->{view_from}} 1881 ) 1882 { 1883 $tmeta = meta_record($table, $opt->{ui_meta_view}) || {}; 1884 } 1885 1886 # This section checks the passed options and converts them from 1887 # strings to refs if necessary 1888 FORMATS: { 1889 no strict 'refs'; 1890 my $ref; 1891 for(qw/ 1892 append 1893 default 1894 disabled 1895 database 1896 error 1897 extra 1898 field 1899 filter 1900 form 1901 height 1902 help 1903 help_url 1904 label 1905 lookup 1906 lookup_query 1907 js_check 1908 maxlength 1909 meta 1910 options 1911 outboard 1912 override 1913 passed 1914 pre_filter 1915 prepend 1916 template 1917 wid_href 1918 widget 1919 width 1920 / ) 1921 { 1922 next if ref $opt->{$_}; 1923 ($opt->{$_} = {}, next) if ! $opt->{$_}; 1924 my $ref = {}; 1925 my $string = $opt->{$_}; 1926 $string =~ s/^\s+//gm; 1927 $string =~ s/\s+$//gm; 1928 while($string =~ m/^(.+?)=\s*(.+)/mg) { 1929 $ref->{$1} = $2; 1930 } 1931 $opt->{$_} = $ref; 1932 } 1933 } 1934 1935 for(grep length($baseopt->{$_}), @mapdirect) { 1936#::logDebug("checking baseopt->{$_}, baseopt=$baseopt->{$_} tmeta=$tmeta->{$_}"); 1937 $tmeta->{$_} = $baseopt->{$_} unless length $tmeta->{$_}; 1938 } 1939 1940 for(grep defined $tmeta->{$_}, @mapdirect) { 1941#::logDebug("checking tmeta->{$_}, tmeta=$tmeta->{$_} opt=$opt->{$_}"); 1942#::logDebug("opt->{$_} is " . (defined $opt->{$_} ? 'defined' : 'undefined')); 1943 $opt->{$_} = $tmeta->{$_} unless defined $opt->{$_}; 1944 } 1945 1946 if($opt->{cgi}) { 1947 my @extra = qw/ 1948 item_id 1949 item_id_left 1950 ui_clone_id 1951 ui_clone_tables 1952 ui_sequence_edit 1953 /; 1954 for(@extra) { 1955 next if ! defined $CGI->{$_}; 1956 $opt->{$_} = $CGI->{$_}; 1957 } 1958 } 1959 1960 if($opt->{wizard}) { 1961 $opt->{noexport} = 1; 1962 $opt->{next_text} = 'Next -->' unless $opt->{next_text}; 1963 $opt->{back_text} = '<-- Back' unless $opt->{back_text}; 1964 } 1965 else { 1966 $opt->{next_text} = "Ok" unless $opt->{next_text}; 1967 } 1968 $opt->{cancel_text} = 'Cancel' unless $opt->{cancel_text}; 1969 1970 for(qw/ next_text cancel_text back_text/ ) { 1971 $opt->{$_} = errmsg($opt->{$_}); 1972 } 1973 1974 if($opt->{wizard} || $opt->{notable} and ! $opt->{table}) { 1975 $opt->{table} = 'mv_null'; 1976 $Vend::Database{mv_null} = 1977 bless [ 1978 {}, 1979 undef, 1980 [ 'code', 'value' ], 1981 [ 'code' => 0, 'value' => 1 ], 1982 0, 1983 { }, 1984 ], 'Vend::Table::InMemory'; 1985 } 1986 1987 # resolve form defaults 1988 1989 while( my ($k, $v) = each %o_default_var) { 1990 $opt->{$k} ||= $::Variable->{$v}; 1991 } 1992 1993 while( my ($k, $v) = each %o_default_length) { 1994 $opt->{$k} = $v if ! length($opt->{$k}); 1995 } 1996 1997 while( my ($k, $v) = each %o_default_defined) { 1998 $opt->{$k} = $v if ! defined($opt->{$k}); 1999 } 2000 2001 while( my ($k, $v) = each %o_default) { 2002 $opt->{$k} ||= $v; 2003 } 2004 2005 if (! $opt->{inner_table_width}) { 2006 if ($opt->{table_width} =~ /^\d+$/) { 2007 $opt->{inner_table_width} = $opt->{table_width} - 2; 2008 } 2009 elsif($opt->{table_width} =~ /\%/) { 2010 $opt->{inner_table_width} = '100%'; 2011 } 2012 else { 2013 $opt->{inner_table_width} = $opt->{table_width}; 2014 } 2015 } 2016 2017 if (! $opt->{inner_table_height}) { 2018 if ($opt->{table_height} =~ /^\d+$/) { 2019 $opt->{inner_table_height} = $opt->{table_height} - 2; 2020 } 2021 elsif($opt->{table_height} =~ /\%/) { 2022 $opt->{inner_table_height} = '100%'; 2023 } 2024 else { 2025 $opt->{inner_table_height} = $opt->{table_height}; 2026 } 2027 } 2028 2029 if(! $opt->{left_width}) { 2030 if($opt->{table_width} eq '100%') { 2031 $opt->{left_width} = 150; 2032 } 2033 else { 2034 $opt->{left_width} = '30%'; 2035 } 2036 } 2037 2038 if(my $dt = $opt->{display_type}) { 2039 my $sub = $Display_options{$dt}; 2040 $sub and ref($sub) eq 'CODE' and $sub->($opt); 2041 } 2042 2043 # init the row styles 2044 foreach my $rtype (qw/data break combo spacer title/) { 2045 my $mainp = $rtype . '_row_extra'; 2046 my $thing = ''; 2047 for my $ptype (qw/class style align valign width/) { 2048 my $parm = $rtype . '_row_' . $ptype; 2049 $opt->{$parm} ||= $tmeta->{$parm}; 2050 if(defined $opt->{$parm}) { 2051 $thing .= qq{ $ptype="$opt->{$parm}"}; 2052 } 2053 } 2054 $opt->{$mainp} ||= $tmeta->{$mainp}; 2055 if($opt->{$mainp}) { 2056 $thing .= " " . $opt->{$mainp}; 2057 } 2058 $opt->{$mainp} = $thing; 2059 } 2060 2061 # Init the cell styles 2062 2063 for my $ctype (qw/label data widget help break/) { 2064 my $mainp = $ctype . '_cell_extra'; 2065 my $thing = ''; 2066 for my $ptype (qw/class style align valign width/) { 2067 my $parm = $ctype . '_cell_' . $ptype; 2068 $opt->{$parm} ||= $tmeta->{$parm}; 2069 if(defined $opt->{$parm}) { 2070 $thing .= qq{ $ptype="$opt->{$parm}"}; 2071 } 2072 } 2073 $opt->{$mainp} ||= $tmeta->{$mainp}; 2074 if($opt->{$mainp}) { 2075 $thing .= " " . $opt->{$mainp}; 2076 } 2077 $opt->{$mainp} = $thing; 2078 } 2079 2080 # Init the button styles 2081 2082 for my $ctype (qw/ok next back cancel delete reset/) { 2083 my $mainp = $ctype . '_button_extra'; 2084 my $thing = ''; 2085 for my $ptype (qw/class style/) { 2086 my $parm = $ctype . '_button_' . $ptype; 2087 $opt->{$parm} ||= $tmeta->{$parm}; 2088 if(defined $opt->{$parm}) { 2089 $thing .= qq{ $ptype="$opt->{$parm}"}; 2090 } 2091 } 2092 $opt->{$mainp} ||= $tmeta->{$mainp}; 2093 if($opt->{$mainp}) { 2094 $thing .= " " . $opt->{$mainp}; 2095 } 2096 $opt->{$mainp} = $thing; 2097 } 2098 2099 $opt->{ui_data_fields} ||= $opt->{ui_wizard_fields}; 2100 2101 ############################################################### 2102 # Get the field display information including breaks and labels 2103 ############################################################### 2104 if( ! $opt->{ui_data_fields} and ! $opt->{ui_data_fields_all}) { 2105 $opt->{ui_data_fields} = $tmeta->{ui_data_fields} || $tmeta->{options}; 2106 } 2107#::logDebug("fields were=$opt->{ui_data_fields}"); 2108 2109 $opt->{ui_data_fields} =~ s/\r\n/\n/g; 2110 $opt->{ui_data_fields} =~ s/\r/\n/g; 2111 $opt->{ui_data_fields} =~ s/^[ \t]+//mg; 2112 $opt->{ui_data_fields} =~ s/[ \t]+$//mg; 2113 2114 if($opt->{ui_data_fields} =~ /\n\n/) { 2115 my @breaks; 2116 my @break_labels; 2117 my $fstring = "\n\n$opt->{ui_data_fields}"; 2118 while ($fstring =~ s/\n+(?:\n[ \t]*=(.*?)(\*?))?\n+[ \t]*(\w[:.\w]+)/\n$3/) { 2119 push @breaks, $3; 2120 $opt->{start_at} ||= $3 if $2; 2121 push @break_labels, "$3=$1" if $1; 2122 } 2123 $opt->{ui_break_before} = join(" ", @breaks) 2124 if ! $opt->{ui_break_before}; 2125 $opt->{ui_break_before_label} = join(",", @break_labels) 2126 if ! $opt->{ui_break_before_label}; 2127 while($fstring =~ s/\n(.*)[ \t]*\*/\n$1/) { 2128 $opt->{focus_at} = $1; 2129 } 2130 $opt->{ui_data_fields} = $fstring; 2131 } 2132 2133 $opt->{ui_data_fields} ||= $opt->{mv_data_fields}; 2134 $opt->{ui_data_fields} =~ s/^[\s,\0]+//; 2135 $opt->{ui_data_fields} =~ s/[\s,\0]+$//; 2136#::logDebug("fields now=$opt->{ui_data_fields}"); 2137 2138 #### This code is also in main editor routine, change there too! 2139 my $cells_per_span = $opt->{cell_span} || 2; 2140 #### 2141 2142 ## Visual field layout 2143 if($opt->{ui_data_fields} =~ /[\w:.]+[ \t,]+\w+.*\n\w+/) { 2144 my $cs = $opt->{colspan} ||= {}; 2145 my @things = split /\n/, $opt->{ui_data_fields}; 2146 my @rows; 2147 my $max = 0; 2148 for(@things) { 2149 my @cols = split /[\s\0,]+/, $_; 2150 my $cnt = scalar(@cols); 2151 $max = $cnt if $cnt > $max; 2152 push @rows, \@cols; 2153 } 2154 $opt->{across} = $max; 2155 for(@rows) { 2156 my $cnt = scalar(@$_); 2157 if ($cnt < $max) { 2158 my $name = $_->[-1]; 2159 $cs->{$name} = (($max - $cnt) * $cells_per_span) + 1; 2160 } 2161 } 2162 } 2163 2164 #### This code is also in main editor routine, change there too! 2165 my $rowdiv = $opt->{across} || 1; 2166 my $rowcount = 0; 2167 my $span = $rowdiv * $cells_per_span; 2168 #### 2169 2170 # Make standard fixed rows 2171 $opt->{spacer_row} = <<EOF; 2172<tr$opt->{spacer_row_extra}> 2173<td colspan="$span" $opt->{spacer_row_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{spacer_height}" alt="x"></td> 2174</tr> 2175EOF 2176 2177 $opt->{mv_nextpage} = $Global::Variable->{MV_PAGE} 2178 if ! $opt->{mv_nextpage}; 2179 2180 $opt->{form_extra} =~ s/^\s*/ / 2181 if $opt->{form_extra}; 2182 $opt->{form_extra} ||= ''; 2183 2184 $opt->{form_extra} .= qq{ name="$opt->{form_name}"} 2185 if $opt->{form_name}; 2186 2187 $opt->{form_extra} .= qq{ target="$opt->{form_target}"} 2188 if $opt->{form_target}; 2189 2190 $opt->{enctype} = $opt->{file_upload} ? ' enctype="multipart/form-data"' : ''; 2191 2192} 2193# UserTag table-editor Order mv_data_table item_id 2194# UserTag table-editor addAttr 2195# UserTag table-editor AttrAlias clone ui_clone_id 2196# UserTag table-editor AttrAlias table mv_data_table 2197# UserTag table-editor AttrAlias fields ui_data_fields 2198# UserTag table-editor AttrAlias mv_data_fields ui_data_fields 2199# UserTag table-editor AttrAlias key item_id 2200# UserTag table-editor AttrAlias view ui_meta_view 2201# UserTag table-editor AttrAlias profile ui_profile 2202# UserTag table-editor AttrAlias email_fields ui_display_only 2203# UserTag table-editor hasEndTag 2204# UserTag table-editor MapRoutine Vend::Table::Editor::editor 2205sub editor { 2206 2207 my ($table, $key, $opt, $overall_template) = @_; 2208show_times("begin table editor call item_id=$key") if $Global::ShowTimes; 2209 2210#::logDebug("overall_template=$overall_template\nin=$opt->{overall_template}"); 2211 use vars qw/$Tag/; 2212 2213 editor_init($opt); 2214 2215 my @messages; 2216 my @errors; 2217 my $pass_return_to; 2218 my $hidden = $opt->{hidden} ||= {}; 2219 2220#::logDebug("key at beginning: $key"); 2221 $opt->{mv_data_table} = $table if $table; 2222 $opt->{table} = $opt->{mv_data_table}; 2223 $opt->{ui_meta_view} ||= $CGI->{ui_meta_view} if $opt->{cgi}; 2224 2225 $key ||= $opt->{item_id}; 2226 2227 if($opt->{cgi}) { 2228 $key ||= $CGI->{item_id}; 2229 unless($opt->{ui_multi_key} = $CGI->{ui_multi_key}) { 2230 $opt->{item_id_left} ||= $CGI::values{item_id_left}; 2231 $opt->{ui_sequence_edit} ||= $CGI::values{ui_sequence_edit}; 2232 } 2233 } 2234 2235 if($opt->{ui_sequence_edit} and ! $opt->{ui_multi_key}) { 2236 delete $opt->{ui_sequence_edit}; 2237 my $left = delete $opt->{item_id_left}; 2238 2239 if(! $key) { 2240#::logDebug("No key, getting from $left"); 2241 if($left =~ s/(.*?)[\0,]// ) { 2242 $key = $opt->{item_id} = $1; 2243 $hidden->{item_id_left} = $left; 2244 $hidden->{ui_sequence_edit} = 1; 2245 } 2246 elsif($left) { 2247 $key = $opt->{item_id} = $left; 2248 } 2249#::logDebug("No key, left now $left"); 2250 } 2251 elsif($left) { 2252#::logDebug("Key, leaving left $left"); 2253 $hidden->{item_id_left} = $left; 2254 $hidden->{ui_sequence_edit} = 1; 2255 } 2256 } 2257 2258 $opt->{item_id} = $key; 2259 2260 $pass_return_to = save_cgi() if $hidden->{ui_sequence_edit}; 2261 2262 my $data; 2263 my $exists; 2264 my $db; 2265 my $multikey; 2266 2267 ## Try and sneak a peek at the data so we can determine views and 2268 ## maybe some other stuff -- we definitely need table/key or a 2269 ## clone id 2270 unless($opt->{notable}) { 2271 # From Vend::Data 2272 my $tab = $table || $opt->{mv_data_table} || $CGI->{mv_data_table}; 2273 my $key = $opt->{item_id} || $CGI->{item_id}; 2274 $db = database_exists_ref($tab); 2275 2276 if($db) { 2277 $multikey = $db->config('COMPOSITE_KEY'); 2278 if($multikey and $key !~ /\0/) { 2279 $key =~ s/-_NULL_-/\0/g; 2280 } 2281 if($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) { 2282 $data = $db->row_hash($opt->{ui_clone_id}); 2283 } 2284 elsif ($key and $db->record_exists($key)) { 2285 $data = $db->row_hash($key); 2286 $exists = 1; 2287 } 2288 2289 if(! $exists and $multikey) { 2290 $data = {}; 2291 eval { 2292 my @inits = split /\0/, $key; 2293 for(@{$db->config('_Key_columns')}) { 2294 $data->{$_} = shift @inits; 2295 } 2296 }; 2297 } 2298 } 2299 } 2300 2301 my $regin = $opt->{all_opts} ? 1 : 0; 2302 2303 resolve_options($opt, undef, $data); 2304 2305 $Trailer = $opt->{xhtml} ? '/' : ''; 2306 if($regin) { 2307 ## Must reset these in case they get set from all_opts. 2308 $hidden = $opt->{hidden}; 2309 } 2310 $overall_template = $opt->{overall_template} 2311 if $opt->{overall_template}; 2312 2313 $table = $opt->{table}; 2314 $key = $opt->{item_id}; 2315 if($opt->{save_meta}) { 2316 $::Scratch->{$opt->{save_meta}} = uneval($opt); 2317 } 2318#::logDebug("key after resolve_options: $key"); 2319 2320#::logDebug("cell_span=$opt->{cell_span}"); 2321 #### This code is also in resolve_options routine, change there too! 2322 my $rowdiv = $opt->{across} || 1; 2323 my $cells_per_span = $opt->{cell_span} || 2; 2324 my $rowcount = 0; 2325 my $span = $rowdiv * $cells_per_span; 2326 #### 2327 2328 my $oddspan = $span - 1; 2329 my $def = $opt->{default_ref} || $::Values; 2330 2331 my $append = $opt->{append}; 2332 my $check = $opt->{check}; 2333 my $class = $opt->{class} || {}; 2334 my $database = $opt->{database}; 2335 my $default = $opt->{default}; 2336 my $disabled = $opt->{disabled}; 2337 my $error = $opt->{error}; 2338 my $extra = $opt->{extra}; 2339 my $field = $opt->{field}; 2340 my $filter = $opt->{filter}; 2341 my $form = $opt->{form}; 2342 my $height = $opt->{height}; 2343 my $help = $opt->{help}; 2344 my $help_url = $opt->{help_url}; 2345 my $label = $opt->{label}; 2346 my $wid_href = $opt->{wid_href}; 2347 my $lookup = $opt->{lookup}; 2348 my $lookup_query = $opt->{lookup_query}; 2349 my $meta = $opt->{meta}; 2350 my $js_check = $opt->{js_check}; 2351 my $maxlength = $opt->{maxlength}; 2352 my $opts = $opt->{opts}; 2353 my $options = $opt->{options}; 2354 my $outboard = $opt->{outboard}; 2355 my $override = $opt->{override}; 2356 my $passed = $opt->{passed}; 2357 my $pre_filter = $opt->{pre_filter}; 2358 my $prepend = $opt->{prepend}; 2359 my $template = $opt->{template}; 2360 my $widget = $opt->{widget}; 2361 my $width = $opt->{width}; 2362 my $colspan = $opt->{colspan} || {}; 2363 2364 my $blabel = $opt->{blabel}; 2365 my $elabel = $opt->{elabel}; 2366 my $mlabel = ''; 2367 my $hidden_all = $opt->{hidden_all} ||= {}; 2368#::logDebug("hidden_all=" . ::uneval($hidden_all)); 2369 my $ntext; 2370 my $btext; 2371 my $ctext; 2372 2373 if($pass_return_to) { 2374 delete $::Scratch->{$opt->{next_text}}; 2375 } 2376 elsif (! $opt->{wizard} and ! $opt->{nosave}) { 2377 $ntext = $Tag->return_to('click', 1); 2378 $ctext = $ntext . "\nmv_todo=back"; 2379 } 2380 else { 2381 if($opt->{action_click}) { 2382 $ntext = <<EOF; 2383mv_todo=$opt->{wizard_next} 2384ui_wizard_action=Next 2385mv_click=$opt->{action_click} 2386EOF 2387 } 2388 else { 2389 $ntext = <<EOF; 2390mv_todo=$opt->{wizard_next} 2391ui_wizard_action=Next 2392mv_click=ui_override_next 2393EOF 2394 } 2395 $::Scratch->{$opt->{next_text}} = $ntext; 2396 2397 my $hidgo = $opt->{mv_cancelpage} || $opt->{hidden}{ui_return_to} || $CGI->{return_to}; 2398 $hidgo =~ s/\0.*//s; 2399 $ctext = $::Scratch->{$opt->{cancel_text}} = <<EOF; 2400mv_form_profile= 2401ui_wizard_action=Cancel 2402mv_nextpage=$hidgo 2403mv_todo=$opt->{wizard_cancel} 2404EOF 2405 if($opt->{mv_prevpage}) { 2406 $btext = $::Scratch->{$opt->{back_text}} = <<EOF; 2407mv_form_profile= 2408ui_wizard_action=Back 2409mv_nextpage=$opt->{mv_prevpage} 2410mv_todo=$opt->{wizard_next} 2411EOF 2412 } 2413 else { 2414 delete $opt->{back_text}; 2415 } 2416 } 2417 2418 for(qw/next_text back_text cancel_text/) { 2419 $opt->{"orig_$_"} = $opt->{$_}; 2420 } 2421 2422 $::Scratch->{$opt->{next_text}} = $ntext if $ntext; 2423 $::Scratch->{$opt->{cancel_text}} = $ctext if $ctext; 2424 $::Scratch->{$opt->{back_text}} = $btext if $btext; 2425 2426 $opt->{next_text} = HTML::Entities::encode($opt->{next_text}, $ESCAPE_CHARS::std); 2427 $opt->{back_text} = HTML::Entities::encode($opt->{back_text}, $ESCAPE_CHARS::std); 2428 $opt->{cancel_text} = HTML::Entities::encode($opt->{cancel_text}, $ESCAPE_CHARS::std); 2429 2430 $::Scratch->{$opt->{next_text}} = $ntext if $ntext; 2431 $::Scratch->{$opt->{cancel_text}} = $ctext if $ctext; 2432 $::Scratch->{$opt->{back_text}} = $btext if $btext; 2433 2434 undef $opt->{tabbed} if $::Scratch->{ui_old_browser}; 2435 undef $opt->{auto_secure} if $opt->{cgi}; 2436 2437 ### Build the error checking 2438 my $error_show_var = 1; 2439 my $have_errors; 2440 if($opt->{ui_profile} or $check) { 2441 $Tag->error( { all => 1 } ) 2442 unless $CGI->{mv_form_profile} or $opt->{keep_errors}; 2443 my $prof = $opt->{ui_profile} || "&update=yes\n"; 2444 if ($prof =~ s/^\*//) { 2445 # special notation ui_profile="*whatever" means 2446 # use automatic checklist-related profile 2447 my $name = $prof; 2448 $prof = $::Scratch->{"profile_$name"} || "&update=yes\n"; 2449 if ($prof) { 2450 $prof =~ s/^\s*(\w+)[\s=]+required\b/$1=mandatory/mg; 2451 for (grep /\S/, split /\n/, $prof) { 2452 if (/^\s*(\w+)\s*=(.+)$/) { 2453 my $k = $1; my $v = $2; 2454 $v =~ s/\s+$//; 2455 $v =~ s/^\s+//; 2456 $error->{$k} = 1; 2457 $error_show_var = 0 if $v =~ /\S /; 2458 } 2459 } 2460 $prof = '&calc delete $Values->{step_' 2461 . $name 2462 . "}; return 1\n" 2463 . $prof; 2464 ## Un-confuse vi } 2465 $opt->{ui_profile_success} = "&set=step_$name 1"; 2466 } 2467 } 2468 my $success = $opt->{ui_profile_success}; 2469 # make sure profile so far ends with a newline so we can add more 2470 $prof .= "\n" unless $prof =~ /\n\s*\z/; 2471 if(ref $check) { 2472 while ( my($k, $v) = each %$check ) { 2473 next unless length $v; 2474 $error->{$k} = 1; 2475 $v =~ s/\s+$//; 2476 $v =~ s/^\s+//; 2477 $v =~ s/\s+$//mg; 2478 $v =~ s/^\s+//mg; 2479 $v =~ s/^required\b/mandatory/mg; 2480 unless ($v =~ /^\&/m) { 2481 $error_show_var = 0 if $v =~ /\S /; 2482 $v =~ s/^/$k=/mg; 2483 $v =~ s/\n/\n&and\n/g; 2484 } 2485 $prof .= "$v\n"; 2486 } 2487 } 2488 elsif ($check) { 2489 for (@_ = grep /\S/, split /[\s,]+/, $check) { 2490 $error->{$_} = 1; 2491 $prof .= "$_=mandatory\n"; 2492 } 2493 } 2494 2495 ## Enable individual widget checks 2496 $::Scratch->{mv_individual_profile} = 1; 2497 2498 ## Call the profile in the form 2499 $opt->{hidden}{mv_form_profile} = 'ui_profile'; 2500 my $fail = $opt->{mv_failpage} || $Global::Variable->{MV_PAGE}; 2501 2502 # watch out for early interpolation here! 2503 $::Scratch->{ui_profile} = <<EOF; 2504[perl] 2505#Debug("cancel='$opt->{orig_cancel_text}' back='$opt->{orig_back_text}' click=\$CGI->{mv_click}"); 2506 my \@clicks = split /\\0/, \$CGI->{mv_click}; 2507 2508 for( qq{$opt->{orig_cancel_text}}, qq{$opt->{orig_back_text}}) { 2509#Debug("compare is '\$_'"); 2510 next unless \$_; 2511 my \$cancel = \$_; 2512 for(\@clicks) { 2513#Debug("click is '\$_'"); 2514 return if \$_ eq \$cancel; 2515 } 2516 } 2517 # the following should already be interpolated by the table-editor tag 2518 # before going into scratch ui_profile 2519 return <<'EOP'; 2520$prof 2521&fail=$fail 2522&fatal=1 2523$success 2524mv_form_profile=mandatory 2525&set=mv_todo $opt->{action} 2526EOP 2527[/perl] 2528EOF 2529 $opt->{blabel} = '<span style="font-weight: normal">'; 2530 $opt->{elabel} = '</span>'; 2531 $mlabel = ($opt->{message_label} || ' ' 2532. errmsg('<b>Bold</b> fields are required')); 2533 $have_errors = $Tag->error( { 2534 all => 1, 2535 show_var => $error_show_var, 2536 show_error => 1, 2537 joiner => "<br$Vend::Xtrailer>", 2538 keep => 1} 2539 ); 2540 if($opt->{all_errors} and $have_errors) { 2541 my $title = $opt->{all_errors_title} || errmsg('Errors'); 2542 my $style = $opt->{all_errors_style} || "color: $opt->{color_fail}"; 2543 my %hash = ( 2544 title => $opt->{all_errors_title} || errmsg('Errors'), 2545 style => $opt->{all_errors_style} || "color: $opt->{color_fail}", 2546 errors => $have_errors, 2547 ); 2548 my $tpl = $opt->{all_errors_template} || <<EOF; 2549<p>{TITLE}: 2550<blockquote style="{STYLE}">{ERRORS}</blockquote> 2551</p> 2552EOF 2553 $mlabel .= tag_attr_list($tpl, \%hash, 'uc'); 2554 2555 } 2556 } 2557 ### end build of error checking 2558 2559 my $die = sub { 2560 ::logError(@_); 2561 $::Scratch->{ui_error} .= "<BR>\n" if $::Scratch->{ui_error}; 2562 $::Scratch->{ui_error} .= ::errmsg(@_); 2563 return undef; 2564 }; 2565 2566 unless($opt->{notable}) { 2567 # From Vend::Data 2568 $db = database_exists_ref($table) 2569 or return $die->("table-editor: bad table '%s'", $table); 2570 } 2571 2572 $opt->{ui_data_fields} =~ s/[,\0\s]+/ /g; 2573 2574 if($opt->{ui_wizard_fields}) { 2575 $opt->{ui_display_only} = $opt->{ui_data_fields}; 2576 } 2577 2578 if(! $opt->{ui_data_fields}) { 2579 if( $opt->{notable}) { 2580 ::logError("table_editor: no place to get fields!"); 2581 return ''; 2582 } 2583 else { 2584 $opt->{ui_data_fields} = join " ", $db->columns(); 2585 } 2586 } 2587 2588 my $keycol; 2589 if($opt->{notable}) { 2590 $keycol = $opt->{ui_data_key_name}; 2591 } 2592 else { 2593 $keycol = $opt->{ui_data_key_name} || $db->config('KEY'); 2594 } 2595 2596 ############################################################### 2597 2598 my $linecount; 2599 2600 CANONCOLS: { 2601 my (@cols, %colseen); 2602 2603 for (split /[,\0\s]/, $opt->{ui_data_fields}) { 2604 next if $colseen{$_}++; 2605 push (@cols, $_); 2606 } 2607 2608 $opt->{ui_data_fields} = join " ", @cols; 2609 2610 $linecount = scalar @cols; 2611 } 2612 2613 my $url = $Tag->area('ui'); 2614 2615 my $key_message; 2616 if($opt->{ui_new_item} and ! $opt->{notable}) { 2617 if( ! $db->config('_Auto_number') and ! $db->config('AUTO_SEQUENCE')) { 2618 $db->config('AUTO_NUMBER', '000001'); 2619 $key = $db->autonumber($key); 2620 } 2621 else { 2622 $key = ''; 2623 $opt->{mv_data_auto_number} = 1; 2624 $key_message = errmsg('(new key will be assigned if left blank)'); 2625 } 2626 } 2627 2628 if($opt->{notable}) { 2629 $data = {}; 2630 } 2631 elsif($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) { 2632 $data = $db->row_hash($opt->{ui_clone_id}) 2633 or 2634 return $die->('table-editor: row_hash function failed for %s.', $key); 2635 $data->{$keycol} = $key; 2636 } 2637 elsif ($db->record_exists($key)) { 2638 $data = $db->row_hash($key); 2639 $exists = 1; 2640 } 2641 2642 if ($opt->{reload} and $have_errors) { 2643 if($data) { 2644 for(keys %$data) { 2645 $data->{$_} = $CGI->{$_} 2646 if defined $CGI->{$_}; 2647 } 2648 } 2649 else { 2650 $data = { %$CGI }; 2651 } 2652 } 2653 2654 2655 my $blob_data; 2656 my $blob_widget; 2657 if($opt->{mailto} and $opt->{mv_blob_field}) { 2658 $opt->{hidden}{mv_blob_only} = 1; 2659 $opt->{hidden}{mv_blob_nick} 2660 = $opt->{mv_blob_nick} 2661 || POSIX::strftime("%Y%m%d%H%M%S", localtime()); 2662 } 2663 elsif($opt->{mv_blob_field}) { 2664#::logDebug("checking blob"); 2665 2666 my $blob_pointer; 2667 $blob_pointer = $data->{$opt->{mv_blob_pointer}} 2668 if $opt->{mv_blob_pointer}; 2669 $blob_pointer ||= $opt->{mv_blob_nick}; 2670 2671 2672 DOBLOB: { 2673 2674 unless ( $db->column_exists($opt->{mv_blob_field}) ) { 2675 push @errors, ::errmsg( 2676 "blob field %s not in database.", 2677 $opt->{mv_blob_field}, 2678 ); 2679 last DOBLOB; 2680 } 2681 2682 my $bstring = $data->{$opt->{mv_blob_field}}; 2683 2684#::logDebug("blob: bstring=$bstring"); 2685 2686 my $blob; 2687 2688 if(length $bstring) { 2689 $blob = $Vend::Interpolate::safe_safe->reval($bstring); 2690 if($@) { 2691 push @errors, ::errmsg("error reading blob data: %s", $@); 2692 last DOBLOB; 2693 } 2694#::logDebug("blob evals to " . ::uneval_it($blob)); 2695 2696 if(ref($blob) !~ /HASH/) { 2697 push @errors, ::errmsg("blob data not a storage book."); 2698 undef $blob; 2699 } 2700 } 2701 else { 2702 $blob = {}; 2703 } 2704 my %wid_data; 2705 my %url_data; 2706 my @labels = keys %$blob; 2707 unshift @labels, ''; 2708 2709 my $extra = ''; 2710 for my $k (keys %$hidden_all) { 2711 my $v = $hidden_all->{$k}; 2712 if(ref($v) eq 'ARRAY') { 2713 for(@$v) { 2714 $extra .= "\n$k=$_"; 2715 } 2716 } 2717 else { 2718 $extra .= "\n$k=$v"; 2719 } 2720 } 2721 2722 for my $key (@labels) { 2723 my $ref; 2724 my $lab; 2725 if($key) { 2726 $ref = $blob->{$key}; 2727 $lab = $ref->{$opt->{mv_blob_label} || 'name'}; 2728 } 2729 else { 2730 $key = ''; 2731 $lab = '--' . errmsg('none') . '--'; 2732 $ref = {}; 2733 } 2734 if($lab) { 2735 $lab =~ s/,/,/g; 2736 $wid_data{$key} = "$key=$key - $lab"; 2737 next unless $key; 2738 $url_data{$key} = $Tag->page( { 2739 href => $Global::Variable->{MV_PAGE}, 2740 form => " 2741 item_id=$opt->{item_id} 2742 mv_blob_nick=$key$extra 2743 ", 2744 }); 2745 $url_data{$key} .= "$key - $lab</a><br$Trailer>"; 2746 } 2747 else { 2748 $wid_data{$key} = $key; 2749 next unless $key; 2750 $url_data{$key} = $Tag->page( { 2751 href => $Global::Variable->{MV_PAGE}, 2752 form => " 2753 item_id=$opt->{item_id} 2754 mv_blob_nick=$key$extra 2755 ", 2756 }); 2757 $url_data{$key} .= "$key</a>"; 2758 } 2759 } 2760#::logDebug("wid_data is " . ::uneval_it(\%wid_data)); 2761 $opt->{mv_blob_title} = "Stored settings" 2762 if ! $opt->{mv_blob_title}; 2763 $opt->{mv_blob_title} = errmsg($opt->{mv_blob_title}); 2764 2765 $::Scratch->{Load} = <<EOF; 2766[return-to type=click stack=1 page="$Global::Variable->{MV_PAGE}"] 2767ui_nextpage= 2768[perl]Log("tried to go to $Global::Variable->{MV_PAGE}"); return[/perl] 2769mv_todo=back 2770EOF 2771#::logDebug("blob_pointer=$blob_pointer blob_nick=$opt->{mv_blob_nick}"); 2772 2773 my $loaded_from; 2774 my $lfrom_msg; 2775 if( $opt->{mv_blob_nick} ) { 2776 $lfrom_msg = $opt->{mv_blob_nick}; 2777 } 2778 else { 2779 $lfrom_msg = errmsg("current values"); 2780 } 2781 $lfrom_msg = errmsg("loaded from %s", $lfrom_msg); 2782 $loaded_from = <<EOF; 2783<i>($lfrom_msg)</i><br> 2784EOF 2785 if(@labels) { 2786 $loaded_from .= errmsg("Load from") . ":<blockquote>"; 2787 $loaded_from .= join (" ", @url_data{ sort keys %url_data }); 2788 $loaded_from .= "</blockquote>"; 2789 } 2790 2791 my $checked; 2792 my $set; 2793 if( $opt->{mv_blob_only} and $opt->{mv_blob_nick}) { 2794 $checked = ' CHECKED'; 2795 $set = $opt->{mv_blob_nick}; 2796 } 2797 2798 unless ($opt->{nosave}) { 2799 $blob_widget = display(undef, undef, undef, { 2800 name => 'mv_blob_nick', 2801 type => $opt->{ui_blob_widget} || 'combo', 2802 filter => 'nullselect', 2803 value => $opt->{mv_blob_nick}, 2804 passed => join (",", @wid_data{ sort keys %wid_data }) || 'default', 2805 }); 2806 my $msg1 = errmsg('Save to'); 2807 my $msg2 = errmsg('Save to book only'); 2808 for (\$msg1, \$msg2) { 2809 $$_ =~ s/ / /g; 2810 } 2811 $blob_widget = <<EOF unless $opt->{ui_blob_hidden}; 2812<b>$msg1:</b> $blob_widget 2813<input type="checkbox" name="mv_blob_only" class="$opt->{widget_class}" value="1"$checked> $msg2</small> 2814EOF 2815 } 2816 2817 $blob_widget = <<EOF unless $opt->{ui_blob_hidden}; 2818<tr$opt->{data_row_extra}> 2819 <td width="$opt->{left_width}"$opt->{label_cell_extra}> 2820 <small>$opt->{mv_blob_title}<br> 2821 $loaded_from 2822 </td> 2823 <td$opt->{data_cell_extra}> 2824 $blob_widget 2825 </td> 2826</tr> 2827 2828<tr> 2829<td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td> 2830</tr> 2831EOF 2832 2833 if($opt->{mv_blob_nick}) { 2834 delete $opt->{force_defaults}; 2835 my @keys = split /::/, $opt->{mv_blob_nick}; 2836 my $ref = $blob->{shift @keys}; 2837 for(@keys) { 2838 my $prior = $ref; 2839 undef $ref; 2840 eval { 2841 $ref = $prior->{$_}; 2842 }; 2843 last DOBLOB unless ref $ref; 2844 } 2845 for(keys %$ref) { 2846 $data->{$_} = $ref->{$_}; 2847 } 2848 } 2849 2850 } 2851 } 2852 2853#::logDebug("data is: " . ::uneval($data)); 2854 $data = { $keycol => $key } 2855 if ! $data; 2856 2857 if(! $opt->{mv_data_function}) { 2858 $opt->{mv_data_function} = $exists ? 'update' : 'insert'; 2859 } 2860 2861 my $url_base = $opt->{secure} ? $Vend::Cfg->{SecureURL} : $Vend::Cfg->{VendURL}; 2862 2863 if(! $opt->{href}) { 2864 $opt->{href} = $opt->{mv_nextpage}; 2865 $opt->{hidden}{mv_ui} = 1 2866 if $Vend::admin and ! defined $opt->{hidden}{mv_ui}; 2867 $opt->{hidden}{mv_action} = $opt->{action}; 2868 } 2869 2870 $opt->{href} = "$url_base/$opt->{href}" 2871 if $opt->{href} !~ m{^(https?:|)/}; 2872 2873 $opt->{method} = $opt->{get} ? 'GET' : 'POST'; 2874 2875 my $wo = $opt->{widgets_only}; 2876 2877 my $restrict_begin; 2878 my $restrict_end; 2879 2880 if($opt->{reparse} and ! $opt->{promiscuous}) { 2881 $restrict_begin = qq{[restrict allow="$opt->{restrict_allow}"]}; 2882 $restrict_end = '[/restrict]'; 2883 } 2884 2885 no strict 'subs'; 2886 2887 chunk ttag(), $restrict_begin; 2888 2889 chunk 'FORM_BEGIN', 'OUTPUT_MAP', <<EOF; 2890<form method="$opt->{method}" action="$opt->{href}"$opt->{enctype}$opt->{form_extra}> 2891EOF 2892 2893 my $prescript_marker = $#out; 2894 2895 $hidden->{mv_click} = $opt->{process_filter}; 2896 $hidden->{mv_todo} = $opt->{action}; 2897 $hidden->{mv_nextpage} = $opt->{mv_nextpage}; 2898 $hidden->{mv_data_table} = $table; 2899 $hidden->{mv_data_key} = $keycol; 2900 if($opt->{cgi}) { 2901 $hidden->{mv_return_table} = $CGI->{mv_return_table} || $table; 2902 } 2903 else { 2904 $hidden->{mv_return_table} = $table; 2905 } 2906 2907 chunk 'HIDDEN_ALWAYS', 'OUTPUT_MAP', <<EOF; 2908<input type="hidden" name="mv_session_id" value="$Vend::Session->{id}"> 2909<input type="hidden" name="mv_click" value="process_filter"> 2910EOF 2911 2912 my @opt_set = (qw/ 2913 ui_meta_specific 2914 ui_hide_key 2915 ui_meta_view 2916 ui_data_decode 2917 mv_blob_field 2918 mv_blob_label 2919 mv_blob_title 2920 mv_blob_pointer 2921 mv_update_empty 2922 mv_data_auto_number 2923 mv_data_function 2924 /); 2925 2926 for my $k (@opt_set) { 2927 $opt->{hidden}{$k} = $opt->{$k}; 2928 } 2929 2930 if($pass_return_to) { 2931 while( my($k, $v) = each %$pass_return_to) { 2932 next if defined $opt->{hidden}{$k}; 2933 $opt->{hidden}{$k} = $pass_return_to->{$k}; 2934 } 2935 } 2936 2937 if($opt->{mailto}) { 2938 $opt->{mailto} =~ s/\s+/ /g; 2939 $::Scratch->{mv_email_enable} = $opt->{mailto}; 2940 $opt->{hidden}{mv_data_email} = 1; 2941 } 2942 2943 $Vend::Session->{ui_return_stack} ||= []; 2944 2945 if($opt->{cgi} and ! $pass_return_to) { 2946 my $r_ary = $Vend::Session->{ui_return_stack}; 2947 2948#::logDebug("ready to maybe push/pop return-to from stack, stack = " . ::uneval($r_ary)); 2949 if($CGI::values{ui_return_stack}++) { 2950 push @$r_ary, $CGI::values{ui_return_to}; 2951 $CGI::values{ui_return_to} = $r_ary->[0]; 2952 } 2953 elsif ($CGI::values{ui_return_to}) { 2954 @$r_ary = ( $CGI::values{ui_return_to} ); 2955 } 2956 chunk 'RETURN_TO', '', $Tag->return_to(); # unless $wo; 2957#::logDebug("return-to stack = " . ::uneval($r_ary)); 2958 } 2959 2960 if(ref $opt->{hidden} or ref $opt->{hidden_all}) { 2961 my ($hk, $hv); 2962 my @o; 2963 while ( ($hk, $hv) = each %$hidden ) { 2964 push @o, produce_hidden($hk, $hv); 2965 } 2966 while ( ($hk, $hv) = each %$hidden_all ) { 2967 push @o, produce_hidden($hk, $hv); 2968 } 2969 chunk 'HIDDEN_USER', 'OUTPUT_MAP', join("", @o); # unless $wo; 2970 } 2971 2972 if($opt->{tabbed}) { 2973 $opt->{table_width} ||= ($opt->{panel_width} || 800) + 10; 2974 $opt->{table_height} ||= ($opt->{panel_height} || 600) + 10; 2975 $opt->{inner_table_width} ||= ($opt->{panel_width} || 800); 2976 $opt->{inner_table_height} ||= ($opt->{panel_height} || 600); 2977 } 2978 chunk ttag(), <<EOF; # unless $wo; 2979<table class="touter" border="0" cellspacing="0" cellpadding="0" width="$opt->{table_width}" height="$opt->{table_height}"> 2980<tr> 2981 <td valign="top"> 2982 2983<table class="tinner" width="$opt->{inner_table_width}" height="$opt->{inner_table_height}" cellspacing="0" cellmargin="0" cellpadding="2" align="center" border="0"> 2984EOF 2985 chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF; # unless $opt->{no_top} or $wo; 2986<tr> 2987<td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td> 2988</tr> 2989EOF 2990 2991 if ($opt->{intro_text}) { 2992#::logDebug("intro_text=$opt->{intro_text}"); 2993 chunk ttag(), <<EOF; 2994<tr $opt->{spacer_row_extra}> 2995 <td colspan="$span" $opt->{spacer_cell_extra}>$opt->{intro_text}</td> 2996</tr> 2997<tr $opt->{title_row_extra}> 2998 <td colspan="$span" $opt->{title_cell_extra}>$::Scratch->{page_title}</td> 2999</tr> 3000EOF 3001 } 3002 3003 $opt->{top_buttons_rows} = 5 unless defined $opt->{top_buttons_rows}; 3004 3005 #### Extra buttons 3006 my $extra_ok = $blob_widget 3007 || $opt->{output_map} 3008 || $linecount >= $opt->{top_buttons_rows} 3009 || defined $opt->{include_form} 3010 || $mlabel; 3011 3012 $mlabel ||= ' '; 3013 if ($extra_ok and ! $opt->{no_top} and ! $opt->{nosave}) { 3014 if($opt->{back_text}) { 3015 chunk ttag(), 'OUTPUT_MAP', <<EOF; # unless $wo; 3016<tr$opt->{data_row_extra}> 3017<td$opt->{label_cell_extra}> </td> 3018<td align="left" colspan="$oddspan"$opt->{data_cell_extra}> 3019EOF 3020 chunk 'COMBINED_BUTTONS_TOP', 'BOTTOM_BUTTONS OUTPUT_MAP', <<EOF; 3021<input type="submit" name="mv_click" value="$opt->{back_text}"$opt->{back_button_extra}> <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> <b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b> 3022<br> 3023EOF 3024 chunk 'MLABEL', 'OUTPUT_MAP', 'MESSAGES', $mlabel; 3025 chunk ttag(), <<EOF; 3026 </td> 3027</tr> 3028$opt->{spacer_row} 3029EOF 3030 } 3031 elsif ($opt->{wizard}) { 3032 chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF; 3033<tr$opt->{data_row_extra}> 3034<td$opt->{label_cell_extra}> </td> 3035<td align="left" colspan="$oddspan"$opt->{data_cell_extra}> 3036EOF 3037 chunk 'WIZARD_BUTTONS_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 3038<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> <b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b> 3039<br> 3040EOF 3041 chunk 'MLABEL', 'NO_TOP OUTPUT_MAP', 'MESSAGES', $mlabel; 3042 chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF; 3043 </td> 3044</tr> 3045$opt->{spacer_row} 3046EOF 3047 } 3048 else { 3049 chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 3050<tr$opt->{data_row_extra}> 3051<td$opt->{label_cell_extra}> </td> 3052<td align="left" colspan="$oddspan"$opt->{data_cell_extra}> 3053EOF 3054 3055 chunk 'OK_TOP', 'NO_TOP OUTPUT_MAP', <<EOF; 3056<input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{ok_button_extra}> 3057EOF 3058 chunk 'CANCEL_TOP', 'NOCANCEL BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 3059 3060<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> 3061EOF 3062 3063 if($opt->{show_reset}) { 3064 chunk 'RESET_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 3065 3066<input type="reset"$opt->{reset_button_extra}> 3067EOF 3068 } 3069 3070 chunk 'MLABEL', 'BOTTOM_BUTTONS OUTPUT_MAP', $mlabel; 3071 chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 3072 </td> 3073</tr> 3074$opt->{spacer_row} 3075EOF 3076 } 3077 } 3078 3079 chunk 'BLOB_WIDGET', $blob_widget; # unless $wo; 3080 3081 #### Extra buttons 3082 3083 if($opt->{ui_new_item} and $opt->{ui_clone_tables}) { 3084 my @sets; 3085 my %seen; 3086 my @tables = split /[\s\0,]+/, $opt->{ui_clone_tables}; 3087 for(@tables) { 3088 if(/:/) { 3089 push @sets, $_; 3090 } 3091 s/:.*//; 3092 } 3093 3094 my %tab_checked; 3095 for(@tables, @sets) { 3096 $tab_checked{$_} = 1 if s/\*$//; 3097 } 3098 3099 @tables = grep ! $seen{$_}++ && defined $Vend::Cfg->{Database}{$_}, @tables; 3100 3101 my $tab = ''; 3102 my $set .= <<'EOF'; 3103[flag type=write table="_TABLES_"] 3104[perl tables="_TABLES_"] 3105 delete $::Scratch->{clone_tables}; 3106 return if ! $CGI->{ui_clone_id}; 3107 return if ! $CGI->{ui_clone_tables}; 3108 my $id = $CGI->{ui_clone_id}; 3109 3110 my $out = "Cloning id=$id..."; 3111 3112 my $new = $CGI->{$CGI->{mv_data_key}} 3113 or do { 3114 $out .= ("clone $id: no mv_data_key '$CGI->{mv_data_key}'"); 3115 $::Scratch->{ui_message} = $out; 3116 return; 3117 }; 3118 3119 if($new =~ /\0/) { 3120 $new =~ s/\0/,/g; 3121 Log("cannot clone multiple keys '$new'."); 3122 return; 3123 } 3124 3125 my %possible; 3126 my @possible = qw/_TABLES_/; 3127 @possible{@possible} = @possible; 3128 my @tables = grep /\S/, split /[\s,\0]+/, $CGI->{ui_clone_tables}; 3129 my @sets = grep /:/, @tables; 3130 @tables = grep $_ !~ /:/, @tables; 3131 for(@tables) { 3132 next unless $possible{$_}; 3133 my $db = $Db{$_} || Vend::Data::database_exists_ref($_); 3134 next unless $db; 3135 my $new = 3136 my $res = $db->clone_row($id, $new); 3137 if($res) { 3138 $out .= "cloned $id to to $new in table $_<BR>\n"; 3139 } 3140 else { 3141 $out .= "FAILED clone of $id to to $new in table $_<BR>\n"; 3142 } 3143 } 3144 for(@sets) { 3145 my ($t, $col) = split /:/, $_; 3146 my $db = $Db{$t} || Vend::Data::database_exists_ref($t) or next; 3147 my $res = $db->clone_set($col, $id, $new); 3148 if($res) { 3149 $out .= "cloned $col=$id to to $col=$new in table $t<BR>\n"; 3150 } 3151 else { 3152 $out .= "FAILED clone of $col=$id to to $col=$new in table $t<BR>\n"; 3153 } 3154 } 3155 $::Scratch->{ui_message} = $out; 3156 return; 3157[/perl] 3158EOF 3159 my $tabform = ''; 3160 @tables = grep $Tag->if_mm( { table => "$_=i" } ), @tables; 3161 3162 for(@tables) { 3163 my $db = Vend::Data::database_exists_ref($_) 3164 or next; 3165 next unless $db->record_exists($opt->{ui_clone_id}); 3166 my $checked = $tab_checked{$_} ? ' CHECKED' : ''; 3167 $tabform .= <<EOF; 3168<input type="checkbox" name="ui_clone_tables" value="$_"$checked> clone to <b>$_</b><br> 3169EOF 3170 } 3171 for(@sets) { 3172 my ($t, $col) = split /:/, $_; 3173 my $checked = $tab_checked{$_} ? ' CHECKED' : ''; 3174 $tabform .= <<EOF; 3175<input type="checkbox" name="ui_clone_tables" value="$_"$checked> clone entries of <b>$t</b> matching on <b>$col</b><br> 3176EOF 3177 } 3178 3179 my $tabs = join " ", @tables; 3180 $set =~ s/_TABLES_/$tabs/g; 3181 $::Scratch->{clone_tables} = $set; 3182 chunk ttag(), <<EOF; # unless $wo; 3183<tr> 3184<td colspan="$span"$opt->{border_cell_extra}> 3185EOF 3186 chunk 'CLONE_TABLES', <<EOF; 3187$tabform<input type="hidden" name="mv_check" value="clone_tables"> 3188<input type="hidden" name="ui_clone_id" value="$opt->{ui_clone_id}"> 3189EOF 3190 chunk ttag(), <<EOF; # unless $wo; 3191</td> 3192</tr> 3193EOF 3194 } 3195 3196 chunk_alias 'TOP_OF_FORM', qw/ FORM_BEGIN /; 3197 chunk_alias 'TOP_BUTTONS', qw/ 3198 COMBINED_BUTTONS_TOP 3199 WIZARD_BUTTONS_TOP 3200 OK_TOP 3201 CANCEL_TOP 3202 RESET_TOP 3203 /; 3204 3205 my %break; 3206 my %break_label; 3207 if($opt->{ui_break_before}) { 3208 my @tmp = grep /\S/, split /[\s,\0]+/, $opt->{ui_break_before}; 3209 @break{@tmp} = @tmp; 3210 if($opt->{ui_break_before_label}) { 3211 @tmp = grep /\S/, split /\s*[,\0]\s*/, $opt->{ui_break_before_label}; 3212 for(@tmp) { 3213 my ($br, $lab) = split /\s*=\s*/, $_, 2; 3214 $break_label{$br} = $lab; 3215 } 3216 } 3217 } 3218 if(!$db and ! $opt->{notable}) { 3219 return "<tr><td>Broken table '$table'</td></tr>"; 3220 } 3221 3222 my $passed_fields = $opt->{ui_data_fields}; 3223 3224 my @extra_cols; 3225 my %email_cols; 3226 my %ok_col; 3227 my @cols; 3228 my @dbcols; 3229 my %display_only; 3230 3231 if($opt->{notable}) { 3232 @cols = split /[\s,\0]+/, $passed_fields; 3233 } 3234 else { 3235 3236 while($passed_fields =~ s/(\w+[.:]+\S+)//) { 3237 push @extra_cols, $1; 3238 } 3239 3240 my @do = grep /\S/, split /[\0,\s]+/, $opt->{ui_display_only}; 3241 for(@do) { 3242#::logDebug("display_only: $_"); 3243 $email_cols{$_} = 1 if $opt->{mailto}; 3244 $display_only{$_} = 1; 3245 push @extra_cols, $_; 3246 } 3247 3248 @dbcols = split /\s+/, $Tag->db_columns( { 3249 name => $table, 3250 columns => $passed_fields, 3251 passed_order => 1, 3252 }); 3253 3254 if($opt->{ui_data_fields}) { 3255 for(@dbcols, @extra_cols) { 3256 unless (/^(\w+)([.:]+)(\S+)/) { 3257 $ok_col{$_} = 1; 3258 next; 3259 } 3260 my $t = $1; 3261 my $s = $2; 3262 my $c = $3; 3263 if($s eq '.') { 3264 $c = $t; 3265 $t = $table; 3266 } 3267 else { 3268 $c =~ s/\..*//; 3269 } 3270 next unless $Tag->db_columns( { name => $t, columns => $c, }); 3271 $ok_col{$_} = 1; 3272 } 3273 } 3274 @cols = grep $ok_col{$_}, split /\s+/, $opt->{ui_data_fields}; 3275 } 3276 3277 $keycol = $cols[0] if ! $keycol; 3278 3279 if($opt->{defaults}) { 3280 if($opt->{force_defaults}) { 3281 $default->{$_} = $def->{$_} for @cols; 3282 } 3283 elsif($opt->{wizard}) { 3284 for(@cols) { 3285 $default->{$_} = $def->{$_} if defined $def->{$_}; 3286 } 3287 } 3288 else { 3289 for(@cols) { 3290 next if defined $default->{$_}; 3291 next unless defined $def->{$_}; 3292 $default->{$_} = $def->{$_}; 3293 } 3294 } 3295 } 3296 3297 my $super = $Tag->if_mm('super'); 3298 3299 my $refkey = $key; 3300 3301 my @data_enable = ($opt->{mv_blob_pointer}, $opt->{mv_blob_field}); 3302 my @ext_enable; 3303 3304 if($opt->{left_width} and ! $opt->{label_cell_width}) { 3305 $opt->{label_cell_extra} .= qq{ width="$opt->{left_width}"}; 3306 } 3307 3308 my $show_meta; 3309 if($super and ! $opt->{no_meta}) { 3310 $show_meta = defined $def->{ui_meta_force} 3311 ? $def->{ui_meta_force} 3312 : $::Variable->{UI_META_LINK}; 3313 } 3314 3315 if($show_meta) { 3316 if(! $opt->{row_template} and ! $opt->{simple_row}) { 3317 $opt->{meta_prepend} = qq{<br$Trailer><font size="1">} 3318 unless defined $opt->{meta_prepend}; 3319 3320 $opt->{meta_append} = '</font>' 3321 unless defined $opt->{meta_append}; 3322 } 3323 else { 3324 $opt->{meta_prepend} ||= ''; 3325 $opt->{meta_append} ||= ''; 3326 } 3327 $opt->{meta_title} ||= errmsg('Edit field meta display info, table %s, column %s'); 3328 $opt->{meta_title_specific} ||= errmsg('Item-specific meta edit, table %s, column %s, key %s'); 3329 $opt->{meta_image_specific} ||= errmsg('specmeta.png'); 3330 $opt->{meta_image} ||= errmsg('meta.png'); 3331 $opt->{meta_image_extra} ||= 'border="0"'; 3332 $opt->{meta_anchor_specific} ||= errmsg('item-specific meta'); 3333 $opt->{meta_anchor} ||= errmsg('meta'); 3334 $opt->{meta_anchor_specific} ||= errmsg('item-specific meta'); 3335 $opt->{meta_extra} = " $opt->{meta_extra}" 3336 if $opt->{meta_extra}; 3337 $opt->{meta_extra} ||= ""; 3338 $opt->{meta_extra} .= qq{ class="$opt->{meta_class}"} 3339 if $opt->{meta_class}; 3340 $opt->{meta_extra} .= qq{ style="$opt->{meta_style}"} 3341 if $opt->{meta_style}; 3342 } 3343 3344 my $row_template = convert_old_template($opt->{row_template}); 3345 3346#::logDebug("display_type='$opt->{display_type}' row_template length=" . length($row_template)); 3347 3348 if(! $row_template) { 3349 $opt->{display_type} = 'simple_row' if $opt->{simple_row}; 3350 $opt->{display_type} ||= 'image_meta' if $opt->{image_meta}; 3351 my $dt = $opt->{display_type} ||= 'default'; 3352 3353 $dt =~ s/-/_/g; 3354 $dt =~ s/\W+//g; 3355#::logDebug("display_type=$dt"); 3356 my $sub = $Display_type{$dt}; 3357 if(ref($sub) eq 'CODE') { 3358 $row_template = $sub->($opt, $span); 3359 } 3360 else { 3361 ::logError("table-editor: display_type '%s' sub not found", $dt); 3362 $row_template = $Display_type{default}->($opt, $span); 3363 } 3364 } 3365 3366 $row_template =~ s/~OPT:(\w+)~/$opt->{$1}/g; 3367 $row_template =~ s/~([A-Z]+)_EXTRA~/$opt->{"\L$1\E_extra"} || $opt->{"\L$1\E_cell_extra"}/g; 3368 3369 $opt->{row_template} = $row_template; 3370 3371 $opt->{combo_template} ||= <<EOF; 3372<tr$opt->{combo_row_extra}><td> {LABEL} </td><td>{WIDGET}</td></tr> 3373EOF 3374 3375 $opt->{break_template} ||= <<EOF; 3376<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr> 3377EOF 3378 3379 my %serialize; 3380 my %serial_data; 3381 3382 if(my $jsc = $opt->{js_changed}) { 3383 $jsc =~ /^\w+$/ 3384 and $jsc = qq{onChange="$jsc} . q{('$$KEY$$','$$COL$$');"}; 3385 foreach my $c (@cols) { 3386 next if $extra->{$c} =~ /\bonchange\s*=/i; 3387 my $tpl = $jsc; 3388 $tpl .= $extra->{$c} if length $extra->{$c}; 3389 $tpl =~ s/\$\$KEY\$\$/$key/g; 3390 $tpl =~ s/\$\$COL\$\$/$c/g; 3391 if ($extra->{$c} and $extra->{$c} =~ /\bonchange\s*=/i) { 3392 $tpl =~ s/onChange="//; 3393 $tpl =~ s/"\s*$/;/; 3394 $extra->{$c} =~ s/\b(onchange\s*=\s*["'])/$1$tpl/i; 3395 } 3396 else { 3397 $extra->{$c} = $tpl; 3398 } 3399 } 3400 } 3401 3402 my %link_row; 3403 my %link_before; 3404 if($opt->{link_table} and $key) { 3405#::logDebug("In link table routines..."); 3406 my @ltable; 3407 my @lfields; 3408 my @lkey; 3409 my @lview; 3410 my @llab; 3411 my @ltpl; 3412 my @lnb; 3413 my @lrq; 3414 my @lra; 3415 my @lrb; 3416 my @lba; 3417 my @lbefore; 3418 my @lsort; 3419 my $tcount = 1; 3420 if(ref($opt->{link_table}) eq 'ARRAY') { 3421 @ltable = @{$opt->{link_table}}; 3422 @lfields = @{$opt->{link_fields}}; 3423 @lview = @{$opt->{link_view}}; 3424 @lkey = @{$opt->{link_key}}; 3425 @llab = @{$opt->{link_label}}; 3426 @ltpl = @{$opt->{link_template}}; 3427 @lnb = @{$opt->{link_no_blank}}; 3428 @lrq = @{$opt->{link_row_qual}}; 3429 @lra = @{$opt->{link_auto_number}}; 3430 @lrb = @{$opt->{link_rows_blank}}; 3431 @lba = @{$opt->{link_blank_auto}}; 3432 @lbefore = @{$opt->{link_before}}; 3433 @lsort = @{$opt->{link_sort}}; 3434 } 3435 else { 3436 @ltable = $opt->{link_table}; 3437 @lfields = $opt->{link_fields}; 3438 @lview = $opt->{link_view}; 3439 @lkey = $opt->{link_key}; 3440 @llab = $opt->{link_label}; 3441 @ltpl = $opt->{link_template}; 3442 @lnb = $opt->{link_no_blank}; 3443 @lrq = $opt->{link_row_qual}; 3444 @lra = $opt->{link_auto_number}; 3445 @lrb = $opt->{link_rows_blank}; 3446 @lba = $opt->{link_blank_auto}; 3447 @lbefore = $opt->{link_before}; 3448 @lsort = $opt->{link_sort}; 3449 } 3450 while(my $lt = shift @ltable) { 3451 my $lf = shift @lfields; 3452 my $lv = shift @lview; 3453 my $lk = shift @lkey; 3454 my $ll = shift @llab; 3455 my $lb = shift @lbefore; 3456 my $lnb = shift @lnb; 3457 my $ls = shift @lsort; 3458 my $lrq = shift @lrq; 3459 my $lra = shift @lra; 3460 my $lrb = shift @lrb; 3461 my $lba = shift @lba; 3462 3463 my $rcount = 0; 3464 3465 $ll ||= errmsg("Settings in table %s linked by %s", $lt, $lk); 3466 3467 my $whash = {}; 3468 3469 my $ldb = database_exists_ref($lt) 3470 or do { 3471 logError("Bad table editor link table: %s", $lt); 3472 next; 3473 }; 3474 3475 my $lmeta = $Tag->meta_record($lt, $lv); 3476 $lf ||= $lmeta->{spread_fields}; 3477 3478 my $l_pkey = $ldb->config('KEY'); 3479 $lrq ||= $l_pkey; 3480 3481 if($lba) { 3482 my @f = grep /\w/, split /[\s,\0]+/, $lf; 3483 @f = grep $_ ne $lk && $_ ne $l_pkey, @f; 3484 $lf = join " ", @f; 3485 } 3486 3487 my $an_piece = ''; 3488 if($lra) { 3489 $an_piece = <<EOF; 3490<input type="hidden" name="mv_data_auto_number__$tcount" value="$lra"> 3491<input type="hidden" name="mv_data_function__$tcount" value="insert"> 3492EOF 3493 } 3494 3495 ## Have to produce two field lists -- one for 3496 ## in link_blank_auto mode (no link_key for row_edit) 3497 ## and one with all when not in link_blank_auto 3498 3499 my @cf = grep /\S/, split /[\s,\0]+/, $lf; 3500 @cf = grep $_ ne $l_pkey, @cf; 3501 $lf = join " ", @cf; 3502 3503 unshift @cf, $lk if $lba; 3504 my $df = join " ", @cf; 3505 3506 my $lextra = $opt->{link_extra} || ''; 3507 $lextra = " $lextra" if $lextra; 3508 3509 my @lout = q{<table cellspacing="0" cellpadding="1">}; 3510 push @lout, qq{<tr><td$lextra> 3511<input type="hidden" name="mv_data_table__$tcount" value="$lt"> 3512<input type="hidden" name="mv_data_fields__$tcount" value="$df"> 3513<input type="hidden" name="mv_data_multiple__$tcount" value="1"> 3514<input type="hidden" name="mv_data_key__$tcount" value="$l_pkey"> 3515<input type="hidden" name="mv_data_multiple_qual__$tcount" value="$lrq"> 3516$an_piece 3517$l_pkey</td>}; 3518 push @lout, $Tag->row_edit({ table => $lt, columns => $lf }); 3519 push @lout, '</tr>'; 3520 3521 my $tname = $ldb->name(); 3522 my $k = $key; 3523 my $lfor = $k; 3524 $lfor = $ldb->quote($key, $lk); 3525 my $q = "SELECT $l_pkey FROM $tname WHERE $lk = $lfor"; 3526 $q .= " ORDER BY $ls" if $ls; 3527 my $ary = $ldb->query($q); 3528 for(@$ary) { 3529 my $rk = $_->[0]; 3530 my $pp = $rcount ? "${rcount}_" : ''; 3531 my $hid = qq{<input type="hidden" name="$pp${l_pkey}__$tcount" value="}; 3532 $hid .= HTML::Entities::encode($rk); 3533 $hid .= qq{">}; 3534 push @lout, qq{<tr><td$lextra>$rk$hid</td>}; 3535 if($lba) { 3536 my $hid = qq{<input type="hidden" name="$pp${lk}__$tcount" value="}; 3537 $hid .= HTML::Entities::encode($k); 3538 $hid .= qq{">}; 3539 push @lout, qq{<td$lextra>$k$hid</td>}; 3540 } 3541 my %o = ( 3542 table => $lt, 3543 key => $_->[0], 3544 extra => $opt->{link_extra}, 3545 pointer => $rcount, 3546 stacker => $tcount, 3547 columns => $lf, 3548 ); 3549 $rcount++; 3550 push @lout, $Tag->row_edit(\%o); 3551 push @lout, "</tr>"; 3552 } 3553 3554 if($lba and $lrq eq $lk || $lrq eq $l_pkey) { 3555 my $colcount = scalar(@cf) + 1; 3556 push @lout, qq{<td colspan="$colcount">Link row qualifier must be different than link_key and primary code when in auto mode.</td>}; 3557 $lnb = 1; 3558 } 3559 3560 unless($lnb) { 3561 my $start_ptr = 999000; 3562 $lrb ||= 1; 3563 for(0 .. $opt->{link_rows_blank}) { 3564 my %o = ( 3565 table => $lt, 3566 blank => 1, 3567 extra => $opt->{link_extra}, 3568 pointer => $start_ptr, 3569 stacker => $tcount, 3570 columns => $lf, 3571 ); 3572 my $ktype = $lba ? 'hidden' : 'text'; 3573 push @lout, qq{<tr><td$lextra>}; 3574 push @lout, qq{<input size="8" type="$ktype" name="${start_ptr}_${l_pkey}__$tcount" value="">}; 3575 push @lout, '(auto)' if $lba; 3576 push @lout, '</td>'; 3577 if($lba) { 3578 my $hid = qq{<input type="hidden" name="${start_ptr}_${lk}__$tcount" value="}; 3579 $hid .= HTML::Entities::encode($k); 3580 $hid .= qq{">}; 3581 push @lout, qq{<td$lextra>$k$hid</td>}; 3582 } 3583 push @lout, $Tag->row_edit(\%o); 3584 push @lout, '</tr>'; 3585 $start_ptr++; 3586 } 3587 } 3588 push @lout, "</table>"; 3589 $whash->{LABEL} = $ll; 3590 $whash->{WIDGET} = join "", @lout; 3591 my $murl = ''; 3592 if($show_meta) { 3593 my $murl; 3594 $murl = $Tag->area({ 3595 href => 'admin/db_metaconfig_spread', 3596 form => qq( 3597 ui_table=$lt 3598 ui_view=$lv 3599 ), 3600 }); 3601 $whash->{META_URL} = $murl; 3602 $whash->{META_STRING} = qq{<a href="$murl"$opt->{meta_extra}>}; 3603 $whash->{META_STRING} .= errmsg('meta') . '</a>'; 3604 3605 } 3606 $whash->{ROW} = 1; 3607 $link_row{$lt} = $whash; 3608 if($lb) { 3609 $link_before{$lb} = $lt; 3610 } 3611 my $mde_key = "mv_data_enable__$tcount"; 3612 $::Scratch->{$mde_key} = "$lt:" . join(",", $l_pkey, @cf) . ':'; 3613 $tcount++; 3614#::logDebug("Made link_table...whash=$whash"); 3615 } 3616 } 3617 3618 if($opt->{include_form}) { 3619#::logDebug("In link table routines..."); 3620 my @icells; 3621 my @ibefore; 3622 my $tcount = 1; 3623 my @includes; 3624 if(ref($opt->{include_form}) eq 'ARRAY') { 3625 @icells = @{delete $opt->{include_form}}; 3626 @ibefore = @{delete $opt->{include_before} || []}; 3627 } 3628 else { 3629 @icells = delete $opt->{include_form}; 3630 @ibefore = delete $opt->{include_before}; 3631 } 3632 3633 $opt->{include_before} = {}; 3634 while(my $it = shift @icells) { 3635 my $ib = shift @ibefore; 3636 3637 my $rcount = 0; 3638 3639#::logDebug("Made include_before=$it"); 3640 if($ib) { 3641 $opt->{include_before}{$ib} = $it; 3642 } 3643 elsif($it =~ /^\s*<tr\b/i) { 3644 push @includes, $it; 3645 } 3646 else { 3647 push @includes, "<tr>$it</tr>"; 3648 } 3649 } 3650 $opt->{include_form} = join "\n", @includes if @includes; 3651 } 3652 3653 if($opt->{tabbed}) { 3654 my $ph = $opt->{panel_height} || '600'; 3655 my $pw = $opt->{panel_width} || '800'; 3656 my $th = $opt->{tab_height} || '30'; 3657 my $oh = $ph + $th; 3658 my $extra = qq{ width="$pw" height="$oh" valign="top"}; 3659 chunk ttag(), qq{<tr><td colspan="$span"$extra>\n}; 3660 } 3661 3662#::logDebug("include_before: " . uneval($opt->{include_before})); 3663 3664 my @extra_hidden; 3665 my $icount = 0; 3666 3667 my $reload; 3668 ## Find out what our errors are 3669 if($CGI->{mv_form_profile} eq 'ui_profile' and $Vend::Session->{errors}) { 3670 for(keys %{$Vend::Session->{errors}}) { 3671 $error->{$_} = 1; 3672 } 3673 $reload = 1 unless $opt->{no_reload}; 3674 } 3675 3676 my @prescript; 3677 my @postscript; 3678 3679 for(qw/callback_prescript callback_postscript/) { 3680 next unless $opt->{$_}; 3681 next if ref($opt->{$_}) eq 'CODE'; 3682 $Tag->error({ 3683 name => errmsg('table-editor'), 3684 set => errmsg('%s is not a code reference', $_), 3685 }); 3686 } 3687 3688 my $callback_prescript = $opt->{callback_prescript} || sub { 3689 push @prescript, @_; 3690 }; 3691 my $callback_postscript = $opt->{callback_postscript} || sub { 3692 push @postscript, @_; 3693 }; 3694 3695 3696 if(my $sheet = $opt->{style_sheet}) { 3697 $sheet =~ s/^\s+//; 3698 $sheet =~ s/\s+$//; 3699 if($Style_sheet{$sheet}) { 3700 push @prescript, $Style_sheet{$sheet}; 3701 } 3702 elsif($sheet =~ /\s/) { 3703 my $pre_put; 3704 if($sheet !~ /^<\w+/) { 3705 $pre_put = 1; 3706 push @prescript, q{<style type="text/css">} 3707 } 3708 push @prescript, $sheet; 3709 push @prescript, q{</style>} if $pre_put; 3710 } 3711 else { 3712 ::logError( 3713 "%s: style sheet %s not found, using default", 3714 errmsg('table-editor'), 3715 $sheet, 3716 ); 3717 push @prescript, $Style_sheet{default}; 3718 } 3719 } 3720 3721 foreach my $col (@cols) { 3722 my $t; 3723 my $c; 3724 my $k; 3725 my $tkey_message; 3726 if($col eq $keycol) { 3727 if($opt->{ui_hide_key}) { 3728 my $kval = $key || $override->{$col} || $default->{$col}; 3729 push @extra_hidden, 3730 qq{<input type="hidden" name="$col" value="$kval">}; 3731 if($break{$col}) { 3732 $titles[$ctl_index] = $break_label{$col}; 3733 } 3734 next; 3735 } 3736 elsif ($opt->{ui_new_item}) { 3737 $tkey_message = $key_message; 3738 } 3739 } 3740 3741 my $w = ''; 3742 my $do = $display_only{$col}; 3743 3744 my $currval; 3745 my $serialize; 3746 3747 if($col =~ /(\w+):+([^:]+)(?::+(\S+))?/) { 3748 $t = $1; 3749 $c = $2; 3750 $c =~ /(.+?)\.\w.*/ 3751 and $col = "$t:$1" 3752 and $serialize = $c; 3753 $k = $3 || undef; 3754 $do = 1 if $disabled->{$c}; 3755 push @ext_enable, ("$t:$c" . $k ? ":$k" : '') 3756 unless $do; 3757 } 3758 else { 3759 $t = $table; 3760 $c = $col; 3761 $c =~ /(.+?)\.\w.*/ 3762 and $col = $1 3763 and $serialize = $c; 3764 $do = 1 if $disabled->{$c}; 3765 push @data_enable, $col 3766 unless $do and ! $opt->{mailto}; 3767 } 3768 3769 my $overridden; 3770 3771 $currval = $data->{$col} if defined $data->{$col}; 3772 if ($opt->{force_defaults} or defined $override->{$c} ) { 3773 $currval = $override->{$c}; 3774 $overridden = 1; 3775#::logDebug("hit override for $col,currval=$currval"); 3776 } 3777 elsif (defined $CGI->{"ui_preload:$t:$c"} ) { 3778 $currval = delete $CGI->{"ui_preload:$t:$c"}; 3779 $overridden = 1; 3780#::logDebug("hit preload for $col,currval=$currval"); 3781 } 3782 elsif( ($do && ! $currval) or $col =~ /:/) { 3783 if(defined $k) { 3784 my $check = $k; 3785 undef $k; 3786 for( $override, $data, $default) { 3787 next unless defined $_->{$check}; 3788 $k = $_->{$check}; 3789 last; 3790 } 3791 } 3792 else { 3793 $k = defined $key ? $key : $refkey; 3794 } 3795 $currval = tag_data($t, $c, $k) if defined $k; 3796#::logDebug("hit display_only for $col, t=$t, c=$c, k=$k, currval=$currval"); 3797 } 3798 elsif (defined $default->{$c} and ! length($data->{$c}) ) { 3799 $currval = $default->{$c}; 3800 $overridden = 1; 3801#::logDebug("hit preload for $col,currval=$currval"); 3802 } 3803 else { 3804#::logDebug("hit data->col for $col, t=$t, c=$c, k=$k, currval=$currval"); 3805 $currval = length($data->{$col}) ? $data->{$col} : ''; 3806 $overridden = 1; 3807 } 3808 3809 if($reload and defined $CGI::values{$col}) { 3810 $currval = $CGI::values{$col}; 3811 } 3812 3813 my $namecol; 3814 if($serialize) { 3815#::logDebug("serialize=$serialize"); 3816 if($serialize{$col}) { 3817 push @{$serialize{$col}}, $serialize; 3818 } 3819 else { 3820 my $sd; 3821 if($col =~ /:/) { 3822 my ($tt, $tc) = split /:+/, $col; 3823 $sd = tag_data($tt, $tc, $k); 3824 } 3825 else { 3826 $sd = $data->{$col} || $default->{$col}; 3827 } 3828#::logDebug("serial_data=$sd"); 3829 $serial_data{$col} = $sd; 3830 $opt->{hidden}{$col} = $data->{$col}; 3831 $serialize{$col} = [$serialize]; 3832 } 3833 $c =~ /\.(.*)/; 3834 my $hk = $1; 3835#::logDebug("fetching serial_data for $col hk=$hk data=$serial_data{$col}"); 3836 $currval = dotted_hash($serial_data{$col}, $hk); 3837#::logDebug("fetched hk=$hk value=$currval"); 3838 $overridden = 1; 3839 $namecol = $c = $serialize; 3840 3841 if($reload and defined $CGI::values{$namecol}) { 3842 $currval = $CGI::values{$namecol}; 3843 } 3844 } 3845 3846 $namecol = $col unless $namecol; 3847 3848#::logDebug("display_only=$do col=$c"); 3849 $widget->{$c} = 'value' 3850 if $do and ! ($disabled->{$c} || $opt->{wizard} || $opt->{mailto}); 3851 3852 if (! length $currval and defined $default->{$c}) { 3853 $currval = $default->{$c}; 3854 } 3855 3856 $template->{$c} ||= $row_template; 3857 3858 my $err_string; 3859 if($error->{$c}) { 3860 my $parm = { 3861 name => $c, 3862 std_label => '$LABEL$', 3863 required => 1, 3864 }; 3865 if($opt->{all_errors}) { 3866 $parm->{keep} = 1; 3867 $parm->{text} = $opt->{error_template} || <<EOF; 3868<font color="$opt->{color_fail}">\$LABEL\$ (%s)</font> 3869[else]{REQUIRED <b>}{LABEL}{REQUIRED </b>}[/else] 3870EOF 3871 } 3872 $err_string = $Tag->error($parm); 3873 if($template->{$c} !~ /{ERROR\??}/) { 3874 $template->{$c} =~ s/{LABEL}/$err_string/g 3875 and 3876 $template->{$c} =~ s/\$LABEL\$/{LABEL}/g; 3877 } 3878 } 3879 3880 my $meta_string = ''; 3881 my $meta_url; 3882 my $meta_url_specific; 3883 if($show_meta) { 3884 # Get global variables 3885 my $base = $::Variable->{UI_BASE} 3886 || $Global::Variable->{UI_BASE} || 'admin'; 3887 my $page = $Global::Variable->{MV_PAGE}; 3888 my $id = $t . "::$c"; 3889 $id = $opt->{ui_meta_view} . "::$id" 3890 if $opt->{ui_meta_view} and $opt->{ui_meta_view} ne 'metaconfig'; 3891 3892 my $return = <<EOF; 3893ui_return_to=$page 3894ui_return_to=item_id=$opt->{item_id} 3895ui_return_to=ui_meta_view=$opt->{ui_meta_view} 3896ui_return_to=mv_return_table=$t 3897mv_return_table=$table 3898ui_return_stack=$CGI->{ui_return_stack} 3899EOF 3900 3901 $meta_url = $Tag->area({ 3902 href => "$base/meta_editor", 3903 form => qq{ 3904 item_id=$id 3905 $return 3906 } 3907 }); 3908 my $meta_specific = ''; 3909 if($opt->{ui_meta_specific}) { 3910 $meta_url_specific = $Tag->area({ 3911 href => "$base/meta_editor", 3912 form => qq{ 3913 item_id=${t}::${c}::$key 3914 $return 3915 } 3916 }); 3917 $meta_specific = <<EOF; 3918<br$Trailer><a href="$meta_url_specific"$opt->{meta_extra} tabindex="9999">$opt->{meta_anchor_specific}</a> 3919EOF 3920 } 3921 3922 $opt->{meta_append} = '</font>' 3923 unless defined $opt->{meta_append}; 3924 if($opt->{image_meta}) { 3925#::logDebug("meta-title=$opt->{meta_title}"); 3926 my $title = errmsg($opt->{meta_title}, $t, $c); 3927 $meta_string = <<EOF; 3928<a href="$meta_url"$opt->{meta_extra} tabindex="9999"><img src="$opt->{meta_image}" title="$title" $opt->{meta_image_extra}></a> 3929EOF 3930 if($meta_specific) { 3931 $title = errmsg($opt->{meta_title_specific}, $t, $c, $key); 3932 $meta_string .= <<EOF; 3933<a href="$meta_url_specific"$opt->{meta_extra} tabindex="9999"><img src="$opt->{meta_image_specific}" title="$title" $opt->{meta_image_extra}></a> 3934EOF 3935 } 3936 } 3937 else { 3938 $meta_string = <<EOF; 3939$opt->{meta_prepend}<a href="$meta_url"$opt->{meta_extra} tabindex="9999">$opt->{meta_anchor}</a> 3940$meta_specific$opt->{meta_append} 3941EOF 3942 } 3943 } 3944 3945 $class->{$c} ||= $opt->{widget_class}; 3946 3947#::logDebug("col=$c currval=$currval widget=$widget->{$c} label=$label->{$c}"); 3948 my $display = display($t, $c, $key, { 3949 append => $append->{$c}, 3950 applylocale => 1, 3951 arbitrary => $opt->{ui_meta_view}, 3952 callback_prescript => $callback_prescript, 3953 callback_postscript => $callback_postscript, 3954 class => $class->{$c}, 3955 column => $c, 3956 db => $database->{$c}, 3957 default => $currval, 3958 default_widget => $opt->{default_widget}, 3959 disabled => $disabled->{$c}, 3960 extra => $extra->{$c}, 3961 fallback => 1, 3962 field => $field->{$c}, 3963 filter => $filter->{$c}, 3964 form => $form->{$c}, 3965 form_name => $opt->{form_name}, 3966 height => $height->{$c}, 3967 help => $help->{$c}, 3968 help_url => $help_url->{$c}, 3969 href => $wid_href->{$c}, 3970 js_check => $js_check->{$c}, 3971 key => $key, 3972 label => $label->{$c}, 3973 lookup => $lookup->{$c}, 3974 lookup_query => $lookup_query->{$c}, 3975 maxlength => $maxlength->{$c}, 3976 meta => $meta->{$c}, 3977 meta_url => $meta_url, 3978 meta_url_specific => $meta_url_specific, 3979 name => $namecol, 3980 options => $options->{$c}, 3981 outboard => $outboard->{$c}, 3982 override => $overridden, 3983 opts => $opts->{$c}, 3984 passed => $passed->{$c}, 3985 pre_filter => $pre_filter->{$c}, 3986 prepend => $prepend->{$c}, 3987 return_hash => 1, 3988 restrict_allow => $opt->{restrict_allow}, 3989 specific => $opt->{ui_meta_specific}, 3990 table => $t, 3991 type => $widget->{$c}, 3992 ui_no_meta_display => $opt->{ui_no_meta_display}, 3993 width => $width->{$c}, 3994 }); 3995#::logDebug("finished display of col=$c"); 3996 my $update_ctl; 3997 3998 if ($display->{WIDGET} =~ /^\s*<input\s[^>]*type\s*=\W*hidden\b[^>]*>\s*$/is) { 3999 push @extra_hidden, $display->{WIDGET}; 4000 next; 4001 } 4002 $display->{TEMPLATE} = $template->{$c}; 4003 $display->{META_STRING} = $meta_string; 4004 $display->{TKEY} = $tkey_message; 4005 $display->{BLABEL} = $blabel; 4006 $display->{ELABEL} = $elabel; 4007 $display->{COLSPAN} = qq{ colspan="$colspan->{$namecol}"} 4008 if $colspan->{$namecol}; 4009 $display->{ERROR} = $err_string; 4010 4011 $update_ctl = 0; 4012 if ($break{$namecol}) { 4013#::logDebug("breaking on $namecol, control index=$ctl_index"); 4014 if(@controls == 0 and @titles == 0) { 4015 $titles[0] = $break_label{$namecol}; 4016 } 4017 elsif(@titles == 0) { 4018 $titles[1] = $break_label{$namecol}; 4019 $update_ctl = 1; 4020 } 4021 else { 4022 push @titles, $break_label{$namecol}; 4023 $update_ctl = 1; 4024 } 4025 } 4026 if($link_before{$col}) { 4027 col_chunk "_SPREAD_$link_before{$col}", 4028 delete $link_row{$link_before{$col}}; 4029 } 4030 if($opt->{include_before} and $opt->{include_before}{$col}) { 4031#::logDebug("include_before: $col $opt->{include_before}{$col}"); 4032 my $chunk = delete $opt->{include_before}{$col}; 4033 if($opt->{include_form_interpolate}) { 4034 $Vend::Interpolate::Tmp->{table_editor_data} = $data; 4035#::logDebug("data to include=" . ::uneval($data)); 4036 $chunk = interpolate_html($chunk); 4037 } 4038 elsif($opt->{include_form_expand}) { 4039 $chunk = expand_values($chunk); 4040#::logDebug("include_before: expanded values on $col $chunk"); 4041 } 4042 my $h = { ROW => $chunk }; 4043 $h->{TEMPLATE} = $opt->{whole_template} || '<tr>{ROW}</tr>'; 4044 col_chunk "_INCLUDE_$col", $h; 4045 } 4046 $ctl_index++ if $update_ctl; 4047 if($opt->{start_at} and $opt->{start_at} eq $namecol) { 4048 $opt->{start_at_index} = $ctl_index; 4049#::logDebug("set start_at_index to $ctl_index"); 4050 } 4051#::logDebug("control index now=$ctl_index"); 4052 col_chunk $namecol, $display; 4053 } 4054 4055 for(sort keys %link_row) { 4056#::logDebug("chunking link_table to _SPREAD_$_"); 4057 col_chunk "_SPREAD_$_", delete $link_row{$_}; 4058 } 4059 4060 my $firstout = scalar(@out); 4061 4062 if($opt->{tabbed}) { 4063 chunk ttag(), qq{</td></tr>\n}; 4064 } 4065 4066 while($rowcount % $rowdiv) { 4067 chunk ttag(), '<td colspan="$cells_per_span"> </td>'; # unless $wo; 4068 $rowcount++; 4069 } 4070 4071 $::Scratch->{mv_data_enable} = ''; 4072 if($opt->{auto_secure}) { 4073 $::Scratch->{mv_data_enable} .= "$table:" . join(",", @data_enable) . ':'; 4074 $::Scratch->{mv_data_enable} .= $opt->{item_id}; 4075 $::Scratch->{mv_data_enable_key} = $opt->{item_id}; 4076 } 4077 if(@ext_enable) { 4078 $::Scratch->{mv_data_enable} .= " " . join(" ", @ext_enable) . " "; 4079 } 4080#::logDebug("setting mv_data_enable to $::Scratch->{mv_data_enable}"); 4081 my @serial = keys %serialize; 4082 my @serial_fields; 4083 my @o; 4084 for (@serial) { 4085#::logDebug("$_ serial_data=$serial_data{$_}"); 4086 $serial_data{$_} = uneval($serial_data{$_}) 4087 if is_hash($serial_data{$_}); 4088 $serial_data{$_} =~ s/\&/&/g; 4089 $serial_data{$_} =~ s/"/"/g; 4090 push @o, qq{<input type="hidden" name="$_" value="$serial_data{$_}">}; # unless $wo; 4091 push @serial_fields, @{$serialize{$_}}; 4092 } 4093 4094 if(! $wo and @serial_fields) { 4095 push @o, qq{<input type="hidden" name="ui_serial_fields" value="}; 4096 push @o, join " ", @serial_fields; 4097 push @o, qq{">}; 4098 chunk 'HIDDEN_SERIAL', 'OUTPUT_MAP', join("", @o); 4099 } 4100 4101 ### 4102 ### Here the user can include some extra stuff in the form.... 4103 ### 4104 if($opt->{include_form}) { 4105 my $chunk = delete $opt->{include_form}; 4106 if($opt->{include_form_interpolate}) { 4107 $chunk = interpolate_html($chunk); 4108 } 4109 elsif($opt->{include_form_expand}) { 4110 $chunk = expand_values($chunk); 4111 } 4112 col_chunk '_INCLUDE_FORM', 4113 { 4114 ROW => $chunk, 4115 TEMPLATE => $opt->{whole_template} || '<tr>{ROW}</tr>', 4116 }; 4117 } 4118 ### END USER INCLUDE 4119 4120 unless ($opt->{mailto} and $opt->{mv_blob_only}) { 4121 @cols = grep ! $display_only{$_} && ! $disabled->{$_}, @cols; 4122 } 4123 $passed_fields = join " ", @cols; 4124 4125 my ($beghid, $endhid) = split m{</td>}i, $opt->{spacer_row}, 2; 4126 $endhid = "</td>$endhid" if $endhid; 4127 chunk ttag(), 'OUTPUT_MAP', $beghid; 4128 chunk 'HIDDEN_EXTRA', 'OUTPUT_MAP', qq{<input type="hidden" name="mv_data_fields" value="$passed_fields">@extra_hidden}; 4129 chunk ttag(), 'OUTPUT_MAP', $endhid; 4130 4131 SAVEWIDGETS: { 4132 last SAVEWIDGETS if $wo || $opt->{nosave}; 4133#::logDebug("in SAVEWIDGETS"); 4134 chunk ttag(), 'OUTPUT_MAP', <<EOF; 4135<tr$opt->{data_row_extra}> 4136<td$opt->{label_cell_extra}> </td> 4137<td align="left" colspan="$oddspan"$opt->{data_cell_extra}> 4138EOF 4139 4140 if($opt->{back_text}) { 4141 4142 chunk 'COMBINED_BUTTONS_BOTTOM', 'OUTPUT_MAP', <<EOF; 4143<input type="submit" name="mv_click" value="$opt->{back_text}"$opt->{back_button_extra}> <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> <b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b> 4144EOF 4145 } 4146 elsif($opt->{wizard}) { 4147 chunk 'WIZARD_BUTTONS_BOTTOM', 'OUTPUT_MAP', <<EOF; 4148<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> <b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b> 4149EOF 4150 } 4151 else { 4152 chunk 'OK_BOTTOM', 'OUTPUT_MAP', <<EOF; 4153<input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{ok_button_extra}> 4154EOF 4155 4156 chunk 'CANCEL_BOTTOM', 'NOCANCEL OUTPUT_MAP', <<EOF; 4157 <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}> 4158EOF 4159 4160 chunk 'RESET_BOTTOM', 'OUTPUT_MAP', qq{ <input type="reset"$opt->{reset_button_extra}>} 4161 if $opt->{show_reset}; 4162 } 4163 4164 4165 if($exists and ! $opt->{nodelete} and $Tag->if_mm('tables', "$table=d")) { 4166 my $key_display = join '/', split /\0/, $key; 4167 my $extra = $Tag->return_to( { type => 'click', tablehack => 1 }); 4168 my $page = $CGI->{ui_return_to}; 4169 $page =~ s/\0.*//s; 4170 my $url = $Tag->area( { 4171 href => $page, 4172 form => qq! 4173 deleterecords=1 4174 ui_delete_id=$key 4175 mv_data_table=$table 4176 mv_click=db_maintenance 4177 mv_action=back 4178 $extra 4179 !, 4180 }); 4181 my $delstr = errmsg('Delete'); 4182 my $delmsg = errmsg('Are you sure you want to delete %s?', $key_display); 4183 if($opt->{output_map} or $opt->{button_delete}) { 4184 chunk 'DELETE_BUTTON', 'NOSAVE OUTPUT_MAP', <<EOF; 4185 4186 <input 4187 type=button 4188 onClick="if(confirm('$delmsg')) { location='$url' }" 4189 title="Delete $key_display" 4190 value="$delstr"$opt->{delete_button_extra}> 4191EOF 4192 } 4193 else { 4194 chunk 'DELETE_BUTTON', 'NOSAVE OUTPUT_MAP', <<EOF; # if ! $opt->{nosave}; 4195<br><br><a onClick="return confirm('$delmsg')" href="$url"><img src="delete.gif" alt="Delete $key_display" border="0"></a> $delstr 4196EOF 4197 } 4198 4199 } 4200 4201 if(! $opt->{notable} and $Tag->if_mm('tables', "$table=x") and ! $db->config('LARGE') ) { 4202 my $checked = ' CHECKED'; 4203 my $msg = errmsg("Automatically export to text file"); 4204 $checked = '' 4205 if defined $opt->{mv_auto_export} and ! $opt->{mv_auto_export}; 4206 my $autoexpstr = errmsg('Auto-export'); 4207 chunk 'AUTO_EXPORT', 'NOEXPORT NOSAVE OUTPUT_MAP', <<EOF; 4208<small> 4209 4210 4211 <input type="checkbox" class="$opt->{widget_class}" title="$msg" name="mv_auto_export" value="$table"$checked><span class="$opt->{widget_class}" title="$msg"> $autoexpstr</span> 4212EOF 4213 4214 } 4215 4216 chunk_alias 'HIDDEN_FIELDS', qw/ 4217 HIDDEN_ALWAYS 4218 HIDDEN_EXTRA 4219 HIDDEN_SERIAL 4220 HIDDEN_USER 4221 /; 4222 chunk_alias 'BOTTOM_BUTTONS', qw/ 4223 WIZARD_BUTTONS_BOTTOM 4224 COMBINED_BUTTONS_BOTTOM 4225 OK_BOTTOM 4226 CANCEL_BOTTOM 4227 RESET_BOTTOM 4228 /; 4229 chunk_alias 'EXTRA_BUTTONS', qw/ 4230 AUTO_EXPORT 4231 DELETE_BUTTON 4232 /; 4233 chunk ttag(), 'OUTPUT_MAP', <<EOF; 4234</small> 4235</td> 4236</tr> 4237EOF 4238 } # end SAVEWIDGETS 4239 4240 my $message = ''; 4241 4242 if(@errors) { 4243 $message .= '<p>Errors:'; 4244 $message .= qq{<font color="$opt->{color_fail}">}; 4245 $message .= '<blockquote>'; 4246 $message .= join "<br>", @errors; 4247 $message .= '</blockquote></font>'; 4248 } 4249 if(@messages) { 4250 $message .= '<p>Messages:'; 4251 $message .= qq{<font color="$opt->{color_success}">}; 4252 $message .= '<blockquote>'; 4253 $message .= join "<br>", @messages; 4254 $message .= '</blockquote></font>'; 4255 } 4256 $Tag->error( { all => 1 } ); 4257 4258 chunk ttag(), 'NO_BOTTOM _MESSAGE', <<EOF; 4259<tr> 4260 <td colspan=$span$opt->{border_cell_extra}> 4261EOF 4262 4263 chunk 'MESSAGE_TEXT', 'NO_BOTTOM', $message; # unless $wo or ($opt->{no_bottom} and ! $message); 4264 4265 chunk ttag(), 'NO_BOTTOM _MESSAGE', <<EOF; 4266 </td> 4267</tr> 4268EOF 4269 4270#::logDebug("tcount=$tcount_all, prior to closing table"); 4271 chunk ttag(), <<EOF; # unless $wo; 4272<tr> 4273<td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td> 4274</tr> 4275</table> 4276</td></tr></table> 4277EOF 4278 4279 my $end_script = ''; 4280 if( $opt->{start_at} || $opt->{focus_at} 4281 and 4282 $opt->{form_name} 4283 and 4284 $widget->{$opt->{start_at}} !~ /radio|check/i 4285 ) 4286 { 4287 my $foc = $opt->{focus_at} || $opt->{start_at}; 4288 $end_script = <<EOF; 4289<script> 4290 document.$opt->{form_name}.$foc.focus(); 4291</script> 4292EOF 4293 } 4294 4295 if($opt->{adjust_cell_class}) { 4296 $end_script .= <<EOF; 4297<script> 4298 var mytags=document.getElementsByTagName("td"); 4299 4300 var max = 0; 4301 var nextmax = 0; 4302 var type = '$opt->{adjust_cell_class}'; 4303 for(var i = 0; i < mytags.length; i++) { 4304 if(mytags[i].getAttribute('class') != type) 4305 continue; 4306 var wid = mytags[i].offsetWidth; 4307 var span = mytags[i].getAttribute('colspan'); 4308 if(span < 2 && mytags[i].getAttribute('class') == type && wid >= max) { 4309 nextmax = max; 4310 max = wid; 4311 } 4312 } 4313 if(max > 500 && (max / 2) > nextmax) 4314 max = nextmax; 4315 for(var i = 0; i < mytags.length; i++) { 4316 if(mytags[i].getAttribute('class') != type) 4317 continue; 4318 if(mytags[i].getAttribute('colspan') > 1) 4319 continue; 4320 mytags[i].setAttribute('width', max); 4321 } 4322</script> 4323EOF 4324 } 4325 4326 if(@prescript) { 4327 my $end = $outhash{FORM_BEGIN}; 4328 $outhash{FORM_BEGIN} = join "\n", $end, @prescript; 4329 } 4330 4331 unshift @postscript, qq{</form>$end_script}; 4332 4333 chunk 'FORM_END', 'OUTPUT_MAP', join("\n", @postscript); 4334 4335 chunk ttag(), $restrict_end; 4336 4337 chunk_alias 'BOTTOM_OF_FORM', qw/ FORM_END /; 4338 4339 my %ehash = ( 4340 ); 4341 for(qw/ 4342 BOTTOM_BUTTONS 4343 NOCANCEL 4344 NOEXPORT 4345 NOSAVE 4346 NO_BOTTOM 4347 OUTPUT_MAP 4348 NO_TOP 4349 SHOW_RESET 4350 /) 4351 { 4352 $ehash{$_} = $opt->{lc $_} ? 1 : 0; 4353 } 4354 4355 $ehash{MESSAGE} = length($message) ? 1 : 0; 4356 4357#::logDebug("exclude is " . uneval(\%exclude)); 4358 4359 if($opt->{output_map}) { 4360 $opt->{output_map} =~ s/^\s+//; 4361 $opt->{output_map} =~ s/\s+$//; 4362 my %map; 4363 my @map = split /[\s,=\0]+/, $opt->{output_map}; 4364 if(@map > 1) { 4365 for(my $i = 0; $i <= @map; $i += 2) { 4366 $map{ uc $map[$i] } = lc $map[$i + 1]; 4367 } 4368 } 4369 else { 4370 %map = qw/ 4371 TOP_OF_FORM top_of_form 4372 BOTTOM_OF_FORM bottom_of_form 4373 HIDDEN_FIELDS hidden_fields 4374 TOP_BUTTONS top_buttons 4375 BOTTOM_BUTTONS bottom_buttons 4376 EXTRA_BUTTONS extra_buttons 4377 /; 4378 } 4379 4380 while(my($al, $to) = each %map) { 4381#::logDebug("outputting alias $al to output $to"); 4382 my $ary = $alias{$al} || []; 4383#::logDebug("alias $al means " . join(" ", @$ary)); 4384 my $string = join("", @outhash{@$ary}); 4385#::logDebug("alias $al string is $string"); 4386 $Tag->output_to($to, { name => $to}, $string ); 4387 } 4388 } 4389 4390 resolve_exclude(\%ehash); 4391 4392 if($wo) { 4393 return (map { @$_ } @controls) if wantarray; 4394 return join "", map { @$_ } @controls; 4395 } 4396show_times("end table editor call item_id=$key") if $Global::ShowTimes; 4397 4398 my @put; 4399 if($overall_template =~ /\S/) { 4400 my $death = sub { 4401 my $item = shift; 4402 logDebug("must have chunk {$item} defined in overall template."); 4403 logError("must have chunk {%s} defined in overall template.", $item); 4404 return undef; 4405 }; 4406 4407 if($opt->{fields_template_only}) { 4408 my $tstart = '<table'; 4409 for my $p (qw/height width cellspacing cellmargin cellpadding class style/) { 4410 my $tag = "table_$p"; 4411 next unless length $opt->{$tag} and $opt->{$tag} =~ /\S/; 4412 my $val = HTML::Entities::encode($opt->{$tag}); 4413 $tstart .= qq{ $p="$val"}; 4414 } 4415 $tstart .= ">"; 4416 $overall_template = qq({TOP_OF_FORM} 4417{HIDDEN_FIELDS} 4418$tstart 4419<tr><td colspan="$span">{TOP_BUTTONS}</td></tr> 4420<tr><td colspan="$span">$overall_template</td></tr> 4421<tr><td colspan="$span">{BOTTOM_BUTTONS}</td></tr> 4422</table> 4423{BOTTOM_OF_FORM} 4424); 4425 } 4426 4427 unless($opt->{incomplete_form_ok}) { 4428 $overall_template =~ /{TOP_OF_FORM}/ 4429 or return $death->('TOP_OF_FORM'); 4430 $overall_template =~ /{HIDDEN_FIELDS}/ 4431 or return $death->('HIDDEN_FIELDS'); 4432 $overall_template =~ /{BOTTOM_OF_FORM}/ 4433 or return $death->('BOTTOM_OF_FORM'); 4434 } 4435 4436 while($overall_template =~ m/\{((?:_INCLUDE_|COLUMN_|_SPREAD_).*?)\}/g) { 4437 my $name = $1; 4438 my $orig = $name; 4439 my $thing = delete $outhash{$name}; 4440#::logDebug("Got to column replace $name, thing=$thing"); 4441 if($name =~ /^_/) { 4442 $overall_template =~ s/\{$name\}/$thing->{ROW}/; 4443 } 4444 elsif($name =~ s/__WIDGET$//) { 4445 $thing = delete $outhash{$name}; 4446 my $lab = "${name}__LABEL"; 4447 my $help = "${name}__HELP"; 4448#::logDebug("Got to widget replace $name, thing=$thing"); 4449 $overall_template =~ s/\{$lab\}/$thing->{LABEL}/; 4450 $overall_template =~ s/\{$help\}/$thing->{HELP}/; 4451 $overall_template =~ s/\{$orig\}/$thing->{WIDGET}/; 4452 } 4453 elsif($thing) { 4454 $overall_template =~ s!\{$name\}! 4455 tag_attr_list($thing->{TEMPLATE}, $thing) 4456 !e; 4457 } 4458 } 4459 while($overall_template =~ m/\{([A-Z_]+)\}/g) { 4460 my $name = $1; 4461 my $thing = delete $outhash{$name}; 4462#::logDebug("Got to random replace $name, thing=$thing"); 4463 next if ! $thing and $alias{$name}; 4464 $overall_template =~ s/\{$name\}/$thing/; 4465 } 4466 while($overall_template =~ m/\{([A-Z_]+)\}/g) { 4467 my $name = $1; 4468 my $thing = delete $alias{$name}; 4469#::logDebug("Got to alias replace $name, thing=$thing"); 4470 $overall_template =~ s/\{$name\}/join "", @outhash{@$thing}/e; 4471 } 4472 my @put; 4473 if($opt->{tabbed}) { 4474 my @tabcont; 4475 for(@controls) { 4476 push @tabcont, create_rows($opt, $_); 4477 } 4478 $opt->{panel_table_extra} ||= 'width="100%" cellpadding="3" cellspacing="1"'; 4479 $opt->{panel_table_extra} =~ s/^/ /; 4480 $opt->{panel_prepend} ||= "<table$opt->{panel_table_extra}>"; 4481 $opt->{panel_append} ||= '</table>'; 4482 push @put, tabbed_display(\@titles,\@tabcont,$opt); 4483 } 4484 else { 4485 my $first = 0; 4486 for(my $i = 0; $i < @controls; $i++) { 4487 push @put, tag_attr_list( 4488 $opt->{break_template}, 4489 { FIRST => ! $first++, ROW => $titles[$i] }, 4490 ) 4491 if $titles[$i]; 4492 push @put, create_rows($opt, $controls[$i]); 4493 } 4494 } 4495 $overall_template =~ s/{:REST}/join "\n", @put/e; 4496#::logDebug("overall_template:\n$overall_template"); 4497 return $overall_template; 4498 } 4499 4500 for(my $i = 0; $i < $firstout; $i++) { 4501#::logDebug("$out[$i] content length=" . length($outhash{$out[$i]} )); 4502 push @put, $outhash{$out[$i]}; 4503 } 4504 4505 if($opt->{tabbed}) { 4506#::logDebug("In tabbed display...controls=" . scalar(@controls) . ", titles=" . scalar(@titles)); 4507 my @tabcont; 4508 for(@controls) { 4509 push @tabcont, create_rows($opt, $_); 4510 } 4511 $opt->{panel_table_extra} ||= 'width="100%" cellpadding="3" cellspacing="1"'; 4512 $opt->{panel_table_extra} =~ s/^/ /; 4513 $opt->{panel_prepend} ||= "<table$opt->{panel_table_extra}>"; 4514 $opt->{panel_append} ||= '</table>'; 4515 push @put, tabbed_display(\@titles,\@tabcont,$opt); 4516 } 4517 else { 4518#::logDebug("titles=" . uneval(\@titles) . "\ncontrols=" . uneval(\@controls)); 4519 my $first = 0; 4520 for(my $i = 0; $i < @controls; $i++) { 4521 push @put, tag_attr_list( 4522 $opt->{break_template}, 4523 { FIRST => ! $first++, ROW => $titles[$i] }, 4524 ) 4525 if $titles[$i]; 4526 push @put, create_rows($opt, $controls[$i]); 4527 } 4528 } 4529 4530 for(my $i = $firstout; $i < @out; $i++) { 4531#::logDebug("$out[$i] content length=" . length($outhash{$out[$i]} )); 4532 push @put, @outhash{$out[$i]}; 4533 } 4534 return join "", @put; 4535} 4536 4537sub convert_old_template { 4538 my $string = shift; 4539 $string =~ s/\$WIDGET\$/{WIDGET}/g 4540 or return $string; 4541 $string =~ s!\{HELP_URL\}(.*)\{/HELP_URL\}!{HELP_URL?}$1\{/HELP_URL?}!gs; 4542 $string =~ s/\$HELP\$/{HELP}/g; 4543 $string =~ s/\$HELP_URL\$/{HELP_URL}/g; 4544 $string =~ s/\~META\~/{META_STRING}/g; 4545 $string =~ s/\$LABEL\$/{LABEL}/g; 4546 $string =~ s/\~ERROR\~/{LABEL}/g; 4547 $string =~ s/\~TKEY\~/{TKEY}/g; 4548 $string =~ s/\~BLABEL\~/{BLABEL}/g; 4549 $string =~ s/\~ELABEL\~/{ELABEL}/g; 4550 return $string; 4551} 4552 4553sub create_rows { 4554 my ($opt, $columns) = @_; 4555 $columns ||= []; 4556 4557 my $rowdiv = $opt->{across} || 1; 4558 my $cells_per_span = $opt->{cell_span} || 2; 4559 my $rowcount = 0; 4560 my $span = $rowdiv * $cells_per_span; 4561 my $oddspan = $span - 1; 4562 my $colspan = $opt->{colspan}; 4563#::logDebug("colspan=" . ::uneval($colspan)); 4564 4565 my @out; 4566 4567 for my $c (@$columns) { 4568 my $colname = $c; 4569 $colname =~ s/^COLUMN_//; 4570#::logDebug("doing column $c name=$colname"); 4571 # If doesn't exist, was brought in before. 4572 my $ref = delete $outhash{$c} 4573 or next; 4574 if($ref->{ROW}) { 4575#::logDebug("outputting ROW $c=$ref->{ROW}"); 4576 my $tpl = $ref->{TEMPLATE} || $opt->{combo_template}; 4577 push @out, tag_attr_list($tpl, $ref); 4578 $rowcount = 0; 4579 next; 4580 } 4581 my $w = ''; 4582 $w .= "<tr$opt->{data_row_extra}>\n" unless $rowcount++ % $rowdiv; 4583 if(my $s = $colspan->{$colname}) { 4584#::logDebug("found colspan=$s (ref=$ref->{COLSPAN}) for $colname"); 4585 my $extra = ($s - 1) / $cells_per_span; 4586 $rowcount += $extra; 4587 } 4588 $w .= tag_attr_list($ref->{TEMPLATE}, $ref); 4589 $w .= "</tr>" unless $rowcount % $rowdiv; 4590 push @out, $w; 4591 } 4592 4593 if($rowcount % $rowdiv) { 4594 my $w = ''; 4595 while($rowcount % $rowdiv) { 4596 $w .= '<td colspan="$cells_per_span"> </td>'; 4597 $rowcount++; 4598 } 4599 $w .= "</tr>"; 4600 push @out, $w; 4601 } 4602 return join "\n", @out; 4603} 4604 46051; 4606