# /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 "