# /tcl/00-ad-utilities.tcl # # Author: ron@arsdigita.com, February 2000 # # This file provides a variety of utilities (originally written by # philg@mit.edu a long time ago) as well as some compatibility # functions to handle differences between AOLserver 2.x and # AOLserver 3.x. # # $Id: utilities.txt,v 1.1 2003/06/14 00:35:55 aegrumet Exp $ proc util_aolserver_2_p {} { if {[string index [ns_info version] 0] == "2"} { return 1 } else { return 0 } } # Define nsv_set/get/exists for AOLserver 2.0 if [util_aolserver_2_p] { uplevel #0 { proc nsv_set { array key value } { return [ns_var set "$array,$key" $value] } proc nsv_get { array key } { return [ns_var get "$array,$key"] } proc nsv_unset {array key } { ns_var unset "$array,$key" } proc nsv_exists { array key } { return [ns_var exists "$array,$key"] } } } # Let's define the nsv arrays out here, so we can call nsv_exists # on their keys without checking to see if it already exists. # we create the array by setting a bogus key. nsv_set proc_source_file . "" proc proc_doc {name args doc_string body} { # let's define the procedure first proc $name $args $body nsv_set proc_doc $name $doc_string # generate a log message for multiply defined scripts if {[nsv_exists proc_source_file $name] && [string compare [nsv_get proc_source_file $name] [info script]] != 0} { ns_log Notice "Multiple definition of $name in [nsv_get proc_source_file $name] and [info script]" } nsv_set proc_source_file $name [info script] } proc proc_source_file_full_path {proc_name} { if ![nsv_exists proc_source_file $proc_name] { return "" } else { set tentative_path [nsv_get proc_source_file $proc_name] regsub -all {/\./} $tentative_path {/} result return $result } } proc_doc util_report_library_entry {{extra_message ""}} "Should be called at beginning of private Tcl library files so that it is easy to see in the error log whether or not private Tcl library files contain errors." { set tentative_path [info script] regsub -all {/\./} $tentative_path {/} scrubbed_path if { [string compare $extra_message ""] == 0 } { set message "Loading $scrubbed_path" } else { set message "Loading $scrubbed_path; $extra_message" } ns_log Notice $message } util_report_library_entry # stuff to process the data that comes # back from the users # if the form looked like # and # then after you run this function you'll have Tcl vars # $foo and $bar set to whatever the user typed in the form # this uses the initially nauseating but ultimately delicious # Tcl system function "uplevel" that lets a subroutine bash # the environment and local vars of its caller. It ain't Common Lisp... proc set_form_variables {{error_if_not_found_p 1}} { if { $error_if_not_found_p == 1} { uplevel { if { [ns_getform] == "" } { ns_returnerror 500 "Missing form data" return } } } else { uplevel { if { [ns_getform] == "" } { # we're not supposed to barf at the user but we want to return # from this subroutine anyway because otherwise we'd get an error return } } } # at this point we know that the form is legal uplevel { set form [ns_getform] set form_size [ns_set size $form] set form_counter_i 0 while {$form_counter_i<$form_size} { set [ns_set key $form $form_counter_i] [ns_set value $form $form_counter_i] incr form_counter_i } } } proc DoubleApos {string} { regsub -all ' "$string" '' result return $result } # if the user types "O'Malley" and you try to insert that into an SQL # database, you will lose big time because the single quote is magic # in SQL and the insert has to look like 'O''Malley'. This function # also trims white space off the ends of the user-typed data. # if the form looked like # and # then after you run this function you'll have Tcl vars # $QQfoo and $QQbar set to whatever the user typed in the form # plus an extra single quote in front of the user's single quotes # and maybe some missing white space proc set_form_variables_string_trim_DoubleAposQQ {} { uplevel { set form [ns_getform] if {$form == ""} { ns_returnerror 500 "Missing form data" return; } set form_size [ns_set size $form] set form_counter_i 0 while {$form_counter_i<$form_size} { set QQ[ns_set key $form $form_counter_i] [DoubleApos [string trim [ns_set value $form $form_counter_i]]] incr form_counter_i } } } # this one does both the regular and the QQ proc set_the_usual_form_variables {{error_if_not_found_p 1}} { if { [ns_getform] == "" } { if $error_if_not_found_p { uplevel { ns_returnerror 500 "Missing form data" return } } else { return } } uplevel { set form [ns_getform] set form_size [ns_set size $form] set form_counter_i 0 while {$form_counter_i<$form_size} { set [ns_set key $form $form_counter_i] [ns_set value $form $form_counter_i] set QQ[ns_set key $form $form_counter_i] [DoubleApos [string trim [ns_set value $form $form_counter_i]]] incr form_counter_i } } } proc set_form_variables_string_trim_DoubleApos {} { uplevel { set form [ns_getform] if {$form == ""} { ns_returnerror 500 "Missing form data" return; } set form_size [ns_set size $form] set form_counter_i 0 while {$form_counter_i<$form_size} { set [ns_set key $form $form_counter_i] [DoubleApos [string trim [ns_set value $form $form_counter_i]]] incr form_counter_i } } } proc set_form_variables_string_trim {} { uplevel { set form [ns_getform] if {$form == ""} { ns_returnerror 500 "Missing form data" return; } set form_size [ns_set size $form] set form_counter_i 0 while {$form_counter_i<$form_size} { set [ns_set key $form $form_counter_i] [string trim [ns_set value $form $form_counter_i]] incr form_counter_i } } } # debugging kludges proc NsSettoTclString {set_id} { set result "" for {set i 0} {$i<[ns_set size $set_id]} {incr i} { append result "[ns_set key $set_id $i] : [ns_set value $set_id $i]\n" } return $result } proc get_referrer {} { return [ns_set get [ns_conn headers] Referer] } proc post_args_to_query_string {} { set arg_form [ns_getform] if {$arg_form!=""} { set form_counter_i 0 while {$form_counter_i<[ns_set size $arg_form]} { append query_return "[ns_set key $arg_form $form_counter_i]=[ns_urlencode [ns_set value $arg_form $form_counter_i]]&" incr form_counter_i } set query_return [string trim $query_return &] } } proc get_referrer_and_query_string {} { if {[ns_conn method]!="GET"} { set query_return [post_args_to_query_string] return "[get_referrer]?${query_return}" } else { return [get_referrer] } } # a philg hack for getting all the values from a set of checkboxes # returns 0 if none are checked, a Tcl list with the values otherwise # terence change: specify default return if none checked proc_doc util_GetCheckboxValues {form checkbox_name {default_return 0}} "For getting all the boxes from a set of checkboxes in a form. This procedure takes the complete ns_conn form and returns a list of checkbox values. It returns 0 if none are found (or some other default return value if specified)." { set i 0 set size [ns_set size $form] while {$i<$size} { if { [ns_set key $form $i] == $checkbox_name} { # LIST_TO_RETURN will be created if it doesn't exist lappend list_to_return [ns_set value $form $i] } incr i } #if no list, you can specify a default return #default default is 0 if { [info exists list_to_return] } { return $list_to_return } else {return $default_return} } # a legacy name that is deprecated proc nmc_GetCheckboxValues {form checkbox_name {default_return 0}} { return [util_GetCheckboxValues $form $checkbox_name $default_return] } ## # Database-related code ## proc nmc_GetNewIDNumber {id_name db} { ns_db dml $db "begin transaction;" ns_db dml $db "update id_numbers set $id_name = $id_name + 1;" set id_number [ns_set value\ [ns_db 1row $db "select unique $id_name from id_numbers;"] 0] ns_db dml $db "end transaction;" return $id_number } # if you do a # set selection [ns_db 1row $db "select foo,bar from my_table where key=37"] # set_variables_after_query # then you will find that the Tcl vars $foo and $bar are set to whatever # the database returned. If you don't like these var names, you can say # set selection [ns_db 1row $db "select count(*) as n_rows from my_table"] # set_variables_after_query # and you will find the Tcl var $n_rows set # You can also use this in a multi-row loop # set selection [ns_db select $db "select *,upper(email) from mailing_list order by upper(email)"] # while { [ns_db getrow $db $selection] } { # set_variables_after_query # ... your code here ... # } # then the appropriate vars will be set during your loop # # CAVEAT NERDOR: you MUST use the variable name "selection" # # # we pick long names for the counter and limit vars # because we don't want them to conflict with names of # database columns or in parent programs # proc set_variables_after_query {} { uplevel { set set_variables_after_query_i 0 set set_variables_after_query_limit [ns_set size $selection] while {$set_variables_after_query_i<$set_variables_after_query_limit} { set [ns_set key $selection $set_variables_after_query_i] [ns_set value $selection $set_variables_after_query_i] incr set_variables_after_query_i } } } # as above, but you must use sub_selection proc set_variables_after_subquery {} { uplevel { set set_variables_after_query_i 0 set set_variables_after_query_limit [ns_set size $sub_selection] while {$set_variables_after_query_i<$set_variables_after_query_limit} { set [ns_set key $sub_selection $set_variables_after_query_i] [ns_set value $sub_selection $set_variables_after_query_i] incr set_variables_after_query_i } } } #same as philg's but you can: #1. specify the name of the "selection" variable #2. append a prefix to all the named variables proc set_variables_after_query_not_selection {selection_variable {name_prefix ""}} { set set_variables_after_query_i 0 set set_variables_after_query_limit [ns_set size $selection_variable] while {$set_variables_after_query_i<$set_variables_after_query_limit} { # NB backslash squarebracket needed since mismatched {} would otherwise mess up value stmt. uplevel " set ${name_prefix}[ns_set key $selection_variable $set_variables_after_query_i] \[ns_set value $selection_variable $set_variables_after_query_i] " incr set_variables_after_query_i } } # takes a query like "select unique short_name from products where product_id = 45" # and returns the result (only works when you are after a single row/column # intersection) proc database_to_tcl_string {db sql} { set selection [ns_db 1row $db $sql] return [ns_set value $selection 0] } proc database_to_tcl_string_or_null {db sql {null_value ""}} { set selection [ns_db 0or1row $db $sql] if { $selection != "" } { return [ns_set value $selection 0] } else { # didn't get anything from the database return $null_value } } #for commands like set full_name ["select first_name, last_name..."] proc database_cols_to_tcl_string {db sql} { set string_to_return "" set selection [ns_db 1row $db $sql] set size [ns_set size $selection] set i 0 while {$i<$size} { append string_to_return " [ns_set value $selection $i]" incr i } return [string trim $string_to_return] } # takes a query like "select product_id from foobar" and returns all # the ids as a Tcl list proc database_to_tcl_list {db sql} { set selection [ns_db select $db $sql] set list_to_return [list] while {[ns_db getrow $db $selection]} { lappend list_to_return [ns_set value $selection 0] } return $list_to_return } proc database_to_tcl_list_list {db sql} { set selection [ns_db select $db $sql] set list_to_return "" while {[ns_db getrow $db $selection]} { set row_list "" set size [ns_set size $selection] set i 0 while {$i<$size} { lappend row_list [ns_set value $selection $i] incr i } lappend list_to_return $row_list } return $list_to_return } proc database_1row_to_tcl_list {db sql} { if [catch {set selection [ns_db 1row $db $sql]} errmsg] { return "" } set list_to_return "" set size [ns_set size $selection] set counter 0 while {$counter<$size} { lappend list_to_return [ns_set value $selection $counter] incr counter } return $list_to_return } proc_doc ad_dbclick_check_dml { db table_name id_column_name generated_id return_url insert_sql } " this proc is used for pages using double click protection. table_name is table_name for which we are checking whether the double click occured. id_column_name is the name of the id table column. generated_id is the generated id, which is supposed to have been generated on the previous page. return_url is url to which this procedure will return redirect in the case of successful insertion in the database. insert_sql is the sql insert statement. if data is ok this procedure will insert data into the database in a double click safe manner and will returnredirect to the page specified by return_url. if database insert fails, this procedure will return a sensible error message to the user." { if [catch { ns_db dml $db $insert_sql } errmsg] { # Oracle choked on the insert # detect double click set selection [ns_db 0or1row $db " select 1 from $table_name where $id_column_name='[DoubleApos $generated_id]'"] if { ![empty_string_p $selection] } { # it's a double click, so just redirect the user to the index page ns_returnredirect $return_url return } ns_log Error "[info script] choked. Oracle returned error: $errmsg" ad_return_error "Error in insert" " We were unable to do your insert in the database. Here is the error that was returned:

	$errmsg
	
" return } ns_returnredirect $return_url return } proc nmc_IllustraDatetoPrettyDate {sql_date} { regexp {(.*)-(.*)-(.*)$} $sql_date match year month day set allthemonths {January February March April May June July August September October November December} # we have to trim the leading zero because Tcl has such a # brain damaged model of numbers and decided that "09-1" # was "8.0" set trimmed_month [string trimleft $month 0] set pretty_month [lindex $allthemonths [expr $trimmed_month - 1]] return "$pretty_month $day, $year" } proc util_IllustraDatetoPrettyDate {sql_date} { regexp {(.*)-(.*)-(.*)$} $sql_date match year month day set allthemonths {January February March April May June July August September October November December} # we have to trim the leading zero because Tcl has such a # brain damaged model of numbers and decided that "09-1" # was "8.0" set trimmed_month [string trimleft $month 0] set pretty_month [lindex $allthemonths [expr $trimmed_month - 1]] return "$pretty_month $day, $year" } # this is the preferred one to use proc_doc util_AnsiDatetoPrettyDate {sql_date} "Converts 1998-09-05 to September 5, 1998" { if ![regexp {(.*)-(.*)-(.*)$} $sql_date match year month day] { return "" } else { set allthemonths {January February March April May June July August September October November December} # we have to trim the leading zero because Tcl has such a # brain damaged model of numbers and decided that "09-1" # was "8.0" set trimmed_month [string trimleft $month 0] set pretty_month [lindex $allthemonths [expr $trimmed_month - 1]] set trimmed_day [string trimleft $day 0] return "$pretty_month $trimmed_day, $year" } } # from the new-utilities.tcl file proc remove_nulls_from_ns_set {old_set_id} { set new_set_id [ns_set new "no_nulls$old_set_id"] for {set i 0} {$i<[ns_set size $old_set_id]} {incr i} { if { [ns_set value $old_set_id $i] != "" } { ns_set put $new_set_id [ns_set key $old_set_id $i] [ns_set value $old_set_id $i] } } return $new_set_id } proc merge_form_with_ns_set {form set_id} { for {set i 0} {$i<[ns_set size $set_id]} {incr i} { set form [ns_formvalueput $form [ns_set key $set_id $i] [ns_set value $set_id $i]] } return $form } proc merge_form_with_query {form db query} { set set_id [ns_db 0or1row $db $query] if { $set_id != "" } { for {set i 0} {$i<[ns_set size $set_id]} {incr i} { set form [ns_formvalueput $form [ns_set key $set_id $i] [ns_set value $set_id $i]] } } return $form } proc bt_mergepiece {htmlpiece values} { # HTMLPIECE is a form usually; VALUES is an ns_set # NEW VERSION DONE BY BEN ADIDA (ben@mit.edu) # Last modification (ben@mit.edu) on Jan ?? 1998 # added support for dates in the date_entry_widget. # # modification (ben@mit.edu) on Jan 12th, 1998 # when the val of an option tag is "", things screwed up # FIXED. # # This used to count the number of vars already introduced # in the form (see remaining num_vars statements), so as # to end early. However, for some unknown reason, this cut off a number # of forms. So now, this processes every tag in the HTML form. set newhtml "" set html_piece_ben $htmlpiece set num_vars 0 for {set i 0} {$i<[ns_set size $values]} {incr i} { if {[ns_set key $values $i] != ""} { set database_values([ns_set key $values $i]) [philg_quote_double_quotes [ns_set value $values $i]] incr num_vars } } set vv {[Vv][Aa][Ll][Uu][Ee]} ; # Sorta obvious set nn {[Nn][Aa][Mm][Ee]} ; # This is too set qq {"([^"]*)"} ; # Matches what's in quotes set pp {([^ ]*)} ; # Matches a word (mind yer pp and qq) set slist {} set count 0 while {1} { incr count set start_point [string first < $html_piece_ben] if {$start_point==-1} { append newhtml $html_piece_ben break; } if {$start_point>0} { append newhtml [string range $html_piece_ben 0 [expr $start_point - 1]] } set end_point [string first > $html_piece_ben] if {$end_point==-1} break incr start_point incr end_point -1 set tag [string range $html_piece_ben $start_point $end_point] incr end_point 2 set html_piece_ben [string range $html_piece_ben $end_point end] set CAPTAG [string toupper $tag] set first_white [string first " " $CAPTAG] set first_word [string range $CAPTAG 0 [expr $first_white - 1]] switch -regexp $CAPTAG { {^INPUT} { if {[regexp {TYPE[ ]*=[ ]*("IMAGE"|"SUBMIT"|"RESET"|IMAGE|SUBMIT|RESET)} $CAPTAG]} { ### # Ignore these ### append newhtml <$tag> } elseif {[regexp {TYPE[ ]*=[ ]*("CHECKBOX"|CHECKBOX)} $CAPTAG]} { # philg and jesse added optional whitespace 8/9/97 ## If it's a CHECKBOX, we cycle through # all the possible ns_set pair to see if it should ## end up CHECKED or not. if {[regexp "$nn=$qq" $tag m nam]} {}\ elseif {[regexp "$nn=$pp" $tag m nam]} {}\ else {set nam ""} if {[regexp "$vv=$qq" $tag m val]} {}\ elseif {[regexp "$vv=$pp" $tag m val]} {}\ else {set val ""} regsub -all {[Cc][Hh][Ee][Cc][Kk][Ee][Dd]} $tag {} tag # support for multiple check boxes provided by michael cleverly if {[info exists database_values($nam)]} { if {[ns_set unique $values $nam]} { if {$database_values($nam) == $val} { append tag " checked" incr num_vars -1 } } else { for {set i [ns_set find $values $nam]} {$i < [ns_set size $values]} {incr i} { if {[ns_set key $values $i] == $nam && [philg_quote_double_quotes [ns_set value $values $i]] == $val} { append tag " checked" incr num_vars -1 break } } } } append newhtml <$tag> } elseif {[regexp {TYPE[ ]*=[ ]*("RADIO"|RADIO)} $CAPTAG]} { ## If it's a RADIO, we remove all the other # choices beyond the first to keep from having ## more than one CHECKED if {[regexp "$nn=$qq" $tag m nam]} {}\ elseif {[regexp "$nn=$pp" $tag m nam]} {}\ else {set nam ""} if {[regexp "$vv=$qq" $tag m val]} {}\ elseif {[regexp "$vv=$pp" $tag m val]} {}\ else {set val ""} #Modified by Ben Adida (ben@mit.edu) so that # the checked tags are eliminated only if something # is in the database. if {[info exists database_values($nam)]} { regsub -all {[Cc][Hh][Ee][Cc][Kk][Ee][Dd]} $tag {} tag if {$database_values($nam)==$val} { append tag " checked" incr num_vars -1 } } append newhtml <$tag> } else { ## If it's an INPUT TYPE that hasn't been covered # (text, password, hidden, other (defaults to text)) ## then we add/replace the VALUE tag if {[regexp "$nn=$qq" $tag m nam]} {}\ elseif {[regexp "$nn=$pp" $tag m nam]} {}\ else {set nam ""} set nam [ns_urldecode $nam] if {[info exists database_values($nam)]} { regsub -all "$vv=$qq" $tag {} tag regsub -all "$vv=$pp" $tag {} tag append tag " value=\"$database_values($nam)\"" incr num_vars -1 } else { if {[regexp {ColValue.([^.]*).([^ ]*)} $tag all nam type]} { set nam [ns_urldecode $nam] set typ "" if {[string match $type "day"]} { set typ "day" } if {[string match $type "year"]} { set typ "year" } if {$typ != ""} { if {[info exists database_values($nam)]} { regsub -all "$vv=$qq" $tag {} tag regsub -all "$vv=$pp" $tag {} tag append tag " value=\"[ns_parsesqldate $typ $database_values($nam)]\"" } } #append tag "> } } {^TEXTAREA} { ### # Fill in the middle of this tag ### if {[regexp "$nn=$qq" $tag m nam]} {}\ elseif {[regexp "$nn=$pp" $tag m nam]} {}\ else {set nam ""} if {[info exists database_values($nam)]} { while {![regexp {^<( *)/[Tt][Ee][Xx][Tt][Aa][Rr][Ee][Aa]} $html_piece_ben]} { regexp {^.[^<]*(.*)} $html_piece_ben m html_piece_ben } append newhtml <$tag>$database_values($nam) incr num_vars -1 } else { append newhtml <$tag> } } {^SELECT} { ### # Set the snam flag, and perhaps smul, too ### set smul [regexp "MULTIPLE" $CAPTAG] set sflg 1 set select_date 0 if {[regexp "$nn=$qq" $tag m snam]} {}\ elseif {[regexp "$nn=$pp" $tag m snam]} {}\ else {set snam ""} set snam [ns_urldecode $snam] # In case it's a date if {[regexp {ColValue.([^.]*).month} $snam all real_snam]} { if {[info exists database_values($real_snam)]} { set snam $real_snam set select_date 1 } } lappend slist $snam append newhtml <$tag> } {^OPTION} { ### # Find the value for this ### if {$snam != ""} { if {[lsearch -exact $slist $snam] != -1} {regsub -all {[Ss][Ee][Ll][Ee][Cc][Tt][Ee][Dd]} $tag {} tag} if {[regexp "$vv *= *$qq" $tag m opt]} {}\ elseif {[regexp "$vv *= *$pp" $tag m opt]} {}\ else { if {[info exists opt]} { unset opt } } # at this point we've figured out what the default from the form was # and put it in $opt (if the default was spec'd inside the OPTION tag # just in case it wasn't, we're going to look for it in the # human-readable part regexp {^([^<]*)(.*)} $html_piece_ben m txt html_piece_ben if {![info exists opt]} { set val [string trim $txt] } else { set val $opt } if {[info exists database_values($snam)]} { # If we're dealing with a date if {$select_date == 1} { set db_val [ns_parsesqldate month $database_values($snam)] } else { set db_val $database_values($snam) } if { ($smul || $sflg) && [string match $db_val $val] } then { append tag " selected" incr num_vars -1 set sflg 0 } } } append newhtml <$tag>$txt } {^/SELECT} { ### # Do we need to add to the end? ### set txt "" if {$snam != ""} { if {[info exists database_values($snam)] && $sflg} { append txt "