# /tcl/ad-utilities.tcl.preload # # 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. # # Author: ron@arsdigita.com, February 2000 # # ad-utilities.tcl.preload,v 3.13.2.3 2000/03/18 02:31:02 ron Exp # 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... ## Security fix ## (patch code from aD) ## # This is an ad-hoc check to make sure users aren't trying to pass in # "naughty" form variables in an effort to hack the database by passing # in SQL. It is called in all instances where a Tcl variable # is set from a form variable. proc check_for_form_variable_naughtiness { name value } { if { [string compare $name user_id] == 0 } { if { [string length $value] > 0 && ![regexp {^[0-9]+$} $value] } { # user_id not null, and not an integer error "The user_id value must be an integer!" } } # This plugs a potentially huge security hole -- michael@cleverly.com if { [string match $name QQ*] } { error "Form variables should never begin with QQ!" } # another bug discovered by Michael Cleverly. if { [string compare $name form_counter_i] == 0} { error "DOS attack attempting to override the form counter" } # extension of Michael Cleverly's above bug, fixed by ben@openforce if { [string compare $name form_size] == 0} { error "DOS attack attempting to override the form size" } } 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 name [ns_set key $form $form_counter_i] set value [ns_set value $form $form_counter_i] check_for_form_variable_naughtiness $name $value set $name $value incr form_counter_i } } } 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 } } } 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 name [ns_set key $form $form_counter_i] set value [ns_set value $form $form_counter_i] check_for_form_variable_naughtiness $name $value set $name $value set QQ[ns_set key $form $form_counter_i] [DoubleApos [string trim $value]] 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 name [ns_set key $form $form_counter_i] set value [ns_set value $form $form_counter_i] check_for_form_variable_naughtiness $name $value set $name [DoubleApos [string trim $value]] 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 name [ns_set key $form $form_counter_i] set value [ns_set value $form $form_counter_i] check_for_form_variable_naughtiness $name $value set $name [string trim $value] 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_doc ad_page_variables {variable_specs} {
Current syntax:

ad_page_variables {var_spec1 [varspec2] ... }

    This proc handles translating form inputs into Tcl variables, and checking
    to see that the correct set of inputs was supplied.  Note that this is mostly a
    check on the proper programming of a set of pages.

Here are the recognized var_specs:

variable; means it's required
{variable default-value}
      Optional, with default value.  If the value is supplied but is null, and the
      default-value is present, that value is used.
{variable -multiple-list}
      The value of the Tcl variable will be a list containing all of the
      values (in order) supplied for that form variable.  Particularly useful
      for collecting checkboxes or select multiples.
      Note that if required or optional variables are specified more than once, the
      first (leftmost) value is used, and the rest are ignored.
{variable -array}
      This syntax supports the idiom of supplying multiple form variables of the
      same name but ending with a "_[0-9]", e.g., foo_1, foo_2.... Each value will be
      stored in the array variable variable with the index being whatever follows the
      underscore.

There is an optional third element in the var_spec.  If it is "QQ", "qq", or
some variant, a variable named "QQvariable" will be created and given the
same value, but with single quotes escaped suitable for handing to SQL.

Other elements of the var_spec are ignored, so a documentation string
describing the variable can be supplied.

Note that the default value form will become the value form in a "set"

Note that the default values are filled in from left to right, and can depend on
values of variables to their left:
ad_page_variables {
    file
    {start 0}
    {end {[expr $start + 20]}}
}
} { set exception_list [list] set form [ns_getform] if { $form != "" } { set form_size [ns_set size $form] set form_counter_i 0 # first pass -- go through all the variables supplied in the form while {$form_counter_i<$form_size} { set variable [ns_set key $form $form_counter_i] set value [ns_set value $form $form_counter_i] check_for_form_variable_naughtiness $variable $value set found "not" # find the matching variable spec, if any foreach variable_spec $variable_specs { if { [llength $variable_spec] >= 2 } { switch -- [lindex $variable_spec 1] { -multiple-list { if { [lindex $variable_spec 0] == $variable } { # variable gets a list of all the values upvar 1 $variable var lappend var $value set found "done" break } } -array { set varname [lindex $variable_spec 0] set pattern "($varname)_(.+)" if { [regexp $pattern $variable match array index] } { if { ![empty_string_p $array] } { upvar 1 $array arr set arr($index) [ns_set value $form $form_counter_i] } set found "done" break } } default { if { [lindex $variable_spec 0] == $variable } { set found "set" break } } } } elseif { $variable_spec == $variable } { set found "set" break } } if { $found == "set" } { upvar 1 $variable var if { ![info exists var] } { # take the leftmost value, if there are multiple ones set var [ns_set value $form $form_counter_i] } } incr form_counter_i } } # now make a pass over each variable spec, making sure everything required is there # and doing defaulting for unsupplied things that aren't required foreach variable_spec $variable_specs { set variable [lindex $variable_spec 0] upvar 1 $variable var if { [llength $variable_spec] >= 2 } { if { ![info exists var] } { set default_value_or_flag [lindex $variable_spec 1] switch -- $default_value_or_flag { -array { # don't set anything } -multiple-list { set var [list] } default { # Needs to be set. uplevel [list eval set $variable "\[subst [list $default_value_or_flag]\]"] # This used to be: # # uplevel [list eval [list set $variable "$default_value_or_flag"]] # # But it wasn't properly performing substitutions. } } } } else { if { ![info exists var] } { lappend exception_list "\"$variable\" required but not supplied" } } # modified by rhs@mit.edu on 1/31/2000 # to QQ everything by default (but not arrays) if {[info exists var] && ![array exists var]} { upvar QQ$variable QQvar set QQvar [DoubleApos $var] } } set n_exceptions [llength $exception_list] # this is an error in the HTML form if { $n_exceptions == 1 } { ns_returnerror 500 [lindex $exception_list 0] return -code return } elseif { $n_exceptions > 1 } { ns_returnerror 500 "
  • [join $exception_list "\n
  • "]\n" return -code return } } # 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 { no_prep 0 }} { if { $no_prep == 1 } { set selection [ns_db 1row $db $sql] } else { set selection [ns_db 1row $db [db_sql_prep $sql]] } return [ns_set value $selection 0] } proc database_to_tcl_string_or_null {db sql {null_value ""}} { set selection [ns_db 0or1row $db [db_sql_prep $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] } proc_doc database_to_tcl_list {db sql} {takes a query like "select product_id from foobar" and returns all the ids as a Tcl list} { set selection [ns_db select $db [db_sql_prep $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_doc database_to_tcl_list_list {db sql} "Returns a list of Tcl lists, with each sublist containing the columns returned by the database; if no rows are returned by the database, returns the empty list (empty string in Tcl 7.x and 8.x)" { set selection [ns_db select $db [db_sql_prep $sql]] set list_to_return [list] 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_doc database_1row_to_tcl_list {db sql} "Returns the column values from one row in the database as a Tcl list. If there isn't exactly one row from this query, throws an error." { set selection [ns_db 1row $db $sql] set list_to_return [list] 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" { set sql_date [string range $sql_date 0 9] 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" } } proc_doc util_AnsiTimestamptoPrettyTimestamp {sql_timestamp} "Converts 1998-09-05 10:00:00 to September 5, 1998 10:00" { ## Add a hack for Postgres dates that include the timestamp # DRB: the right number really is 9 "YYYY-MM-DD" set pretty_date [util_AnsiDatetoPrettyDate [string range $sql_timestamp 0 9]] return "$pretty_date [string range $sql_timestamp 10 end]" } # 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 "
  • [join $exception_list "\n
  • "]\n" return -code return } } proc_doc page_validation {args} { This proc allows page arg, etc. validation. It accepts a bunch of code blocks. Each one is executed, and any error signalled is appended to the list of exceptions. Note that you can customize the complaint page to match the design of your site, by changing the proc called to do the complaining: it's [ad_parameter ComplainProc "" ad_return_complaint] The division of labor between ad_page_variables and page_validation is that ad_page_variables handles programming errors, and does simple defaulting, so that the rest of the Tcl code doesn't have to worry about testing [info exists ...] everywhere. page_validation checks for errors in user input. For virtually all such tests, there is no distinction between "unsupplied" and "null string input". Note that errors are signalled using the Tcl "error" function. This allows nesting of procs which do the validation tests. In addition, validation functions can return useful values, such as trimmed or otherwise munged versions of the input. } { if { [info exists {%%exception_list}] } { error "Something's wrong" } # have to put this in the caller's frame, so that sub_page_validation can see it # that's because the "uplevel" used to evaluate the code blocks hides this frame upvar {%%exception_list} {%%exception_list} set {%%exception_list} [list] foreach validation_block $args { if { [catch {uplevel $validation_block} errmsg] } { lappend {%%exception_list} $errmsg } } set exception_list ${%%exception_list} unset {%%exception_list} set n_exceptions [llength $exception_list] if { $n_exceptions != 0 } { set complain_proc [ad_parameter ComplainProc "" ad_return_complaint] if { $n_exceptions == 1 } { $complain_proc $n_exceptions [lindex $exception_list 0] } else { $complain_proc $n_exceptions "
  • [join $exception_list "\n
  • "]\n" } return -code return } } proc_doc sub_page_validation {args} { Use this inside a page_validation block which needs to check more than one thing. Put this around each part that might signal an error. } { # to allow this to be at any level, we search up the stack for {%%exception_list} set depth [info level] for {set level 1} {$level <= $depth} {incr level} { upvar $level {%%exception_list} {%%exception_list} if { [info exists {%%exception_list}] } { break } } if { ![info exists {%%exception_list}] } { error "sub_page_validation not inside page_validation" } foreach validation_block $args { if { [catch {uplevel $validation_block} errmsg] } { lappend {%%exception_list} $errmsg } } } proc_doc validate_integer {field_name string} "Throws an error if the string isn't a decimal integer; otherwise strips any leading zeros (so this won't work for octals) and returns the result." { if { ![regexp {^[0-9]+$} $string] } { error "The entry for $field_name, \"$string\" is not an integer" } # trim leading zeros, so as not to confuse Tcl set string [string trimleft $string "0"] if { [empty_string_p $string] } { # but not all of the zeros return "0" } return $string } proc_doc validate_integer_or_null {field_name string} "Throws an error if the string isn't a decimal integer; otherwise strips any leading zeros (so this won't work for octals) and returns the result. This also allows empty string" { if { [empty_string_p $string] } { return $string } return [validate_integer $field_name $string] } proc_doc validate_decimal {field_name string} "Throws an error if the string isn't a decimal" { # First check if it's a valid decimal if { [regexp {^[0-9]*\.[0-9]*$} $string] } { return $string } validate_integer $field_name $string } proc_doc validate_decimal_or_null {field_name string} "Throws an error if the string isn't either a decimal, an integer, or null. If the string turns out to be an integer, it strips any leading zeros (so this won't work for octals) and returns the result. Otherwise it just returns the result." { if { [empty_string_p $string] } { return $string } return [validate_decimal $field_name $string] } proc_doc validate_zip_code {field_name db zip_string country_code} "Given a string, signals an error if it's not a legal zip code" { if { $country_code == "" || [string toupper $country_code] == "US" } { if { [regexp {^[0-9][0-9][0-9][0-9][0-9](-[0-9][0-9][0-9][0-9])?$} $zip_string] } { set zip_5 [string range $zip_string 0 4] set selection [ns_db 0or1row $db "select 1 from dual where exists (select 1 from zip_codes where zip_code like '$zip_5%')"] if { $selection == "" } { error "The entry for $field_name, \"$zip_string\" is not a recognized zip code" } } else { error "The entry for $field_name, \"$zip_string\" does not look like a zip code" } } else { if { $zip_string != "" } { error "Zip code is not needed outside the US" } } return $zip_string } proc_doc validate_ad_dateentrywidget {field_name column form {allow_null 0}} { } { set col [ns_urlencode $column] set day [ns_set get $form "ColValue.$col.day"] ns_set update $form "ColValue.$col.day" [string trimleft $day "0"] set month [ns_set get $form "ColValue.$col.month"] set year [ns_set get $form "ColValue.$col.year"] # check that either all elements are blank # date value is formated correctly for ns_dbformvalue if { [empty_string_p "$day$month$year"] } { if { $allow_null == 0 } { error "$field_name must be supplied" } else { return "" } } elseif { ![empty_string_p $year] && [string length $year] != 4 } { error "The year must contain 4 digits." } elseif { [catch { ns_dbformvalue $form $column date date } errmsg ] } { error "The entry for $field_name had a problem: $errmsg." } return $date } proc_doc util_WriteWithExtraOutputHeaders {headers_so_far {first_part_of_page ""}} "Takes in a string of headers to write to an HTTP connection, terminated by a newline. Checks \[ns_conn outputheaders\] and adds those headers if appropriate. Adds two newlines at the end and writes out to the connection. May optionally be used to write the first part of the page as well (saves a packet)" { set set_headers_i 0 set set_headers_limit [ns_set size [ns_conn outputheaders]] while {$set_headers_i < $set_headers_limit} { append headers_so_far "[ns_set key [ns_conn outputheaders] $set_headers_i]: [ns_set value [ns_conn outputheaders] $set_headers_i]\n" incr set_headers_i } append entire_string_to_write $headers_so_far "\n" $first_part_of_page ns_write $entire_string_to_write } # we use this when we want to send out just the headers # and then do incremental ns_writes. This way the user # doesn't have to wait like if you used a single ns_return proc ReturnHeaders {{content_type text/html}} { set all_the_headers "HTTP/1.0 200 OK MIME-Version: 1.0 Content-Type: $content_type\n" util_WriteWithExtraOutputHeaders $all_the_headers } # All the following ReturnHeaders versions are obsolete; # just set [ns_conn outputheaders]. proc ReturnHeadersNoCache {{content_type text/html}} { ns_write "HTTP/1.0 200 OK MIME-Version: 1.0 Content-Type: $content_type pragma: no-cache " } proc ReturnHeadersWithCookie {cookie_content {content_type text/html}} { ns_write "HTTP/1.0 200 OK MIME-Version: 1.0 Content-Type: $content_type Set-Cookie: $cookie_content " } proc ReturnHeadersWithCookieNoCache {cookie_content {content_type text/html}} { ns_write "HTTP/1.0 200 OK MIME-Version: 1.0 Content-Type: $content_type Set-Cookie: $cookie_content pragma: no-cache " } proc_doc ad_return_top_of_page {first_part_of_page {content_type text/html}} "Returns HTTP headers plus the top of the user-ivisible page. Saves a TCP packet (and therefore some overhead) compared to using ReturnHeaders and an ns_write." { set all_the_headers "HTTP/1.0 200 OK MIME-Version: 1.0 Content-Type: $content_type\n" util_WriteWithExtraOutputHeaders $all_the_headers $first_part_of_page } proc_doc apply {func arglist} { Evaluates the first argument with ARGLIST as its arguments, in the environment of its caller. Analogous to the Lisp function of the same name. } { set func_and_args [concat $func $arglist] return [uplevel $func_and_args] } proc_doc safe_eval args { Version of eval that checks its arguments for brackets that may be used to execute unsafe code. } { foreach arg $args { if { [regexp {[\[;]} $arg] } { return -code error "Unsafe argument to safe_eval: $arg" } } return [apply uplevel $args] } proc_doc lmap {list proc_name} {Applies proc_name to each item of the list, appending the result of each call to a new list that is the return value.} { set lmap [list] foreach item $list { lappend lmap [safe_eval $proc_name $item] } return $lmap } # if this hairy proc doesn't work, complain to davis@arsdigita.com proc_doc util_close_html_tags {html_fragment {break_soft 0} {break_hard 0}} { Given an HTML fragment, this procedure will close any tags that have been left open. The optional arguments let you specify that the fragment is to be truncated to a certain number of displayable characters. After break_soft, it truncates and closes open tags unless you're within non-breaking tags (e.g., Af). After break_hard displayable characters, the procedure simply truncates and closes any open HTML tags that might have resulted from the truncation.

    Note that the internal syntax table dictates which tags are non-breaking. The syntax table has codes:

    } { set frag $html_fragment set syn(A) nobr set syn(ADDRESS) nobr set syn(NOBR) nobr # set syn(FORM) discard set syn(TABLE) discard # set syn(BLINK) remove # set syn(FONT) close set syn(B) close set syn(BIG) close set syn(I) close set syn(S) close set syn(SMALL) close set syn(STRIKE) close set syn(SUB) close set syn(SUP) close set syn(TT) close set syn(U) close set syn(ABBR) close set syn(ACRONYM) close set syn(CITE) close set syn(CODE) close set syn(DEL) close set syn(DFN) close set syn(EM) close set syn(INS) close set syn(KBD) close set syn(SAMP) close set syn(STRONG) close set syn(VAR) close set syn(DIR) close set syn(DL) close set syn(MENU) close set syn(OL) close set syn(UL) close set syn(H1) close set syn(H2) close set syn(H3) close set syn(H4) close set syn(H5) close set syn(H6) close set syn(BDO) close set syn(BLOCKQUOTE) close set syn(CENTER) close set syn(DIV) close set syn(PRE) close set syn(Q) close set syn(SPAN) close set out {} set out_len 0 # counts how deep we are nested in nonbreaking tags, tracks the nobr point # and what the nobr string length would be set nobr 0 set nobr_out_point 0 set nobr_tagptr 0 set nobr_len 0 set discard 0 set tagptr -1 # first thing we do is chop off any trailing unclosed tag # since when we substr blobs this sometimes happens # this should in theory cut any tags which have been cut open. while {[regexp {(.*)<[^>]*$} $frag match frag]} {} while { "$frag" != "" } { # here we attempt to cut the string into "pretagposttag" # and build the output list. if {![regexp "(\[^<]*)(<\[ \t]*(/?)(\[^ \t>]+)(\[^>]*)>)?(.*)" $frag match pretag fulltag close tag tagbody frag]} { # should never get here since above will match anything. # puts "NO MATCH: should never happen! frag=$frag" append out $frag set frag {} } else { # puts "\n\nmatch=$match\n pretag=$pretag\n fulltag=$fulltag\n close=$close\n tag=$tag\n tagbody=$tagbody\nfrag=$frag\n\n" if { ! $discard } { # figure out if we can break with the pretag chunk if { $break_soft } { if {! $nobr && [expr [string length $pretag] + $out_len] > $break_soft } { # first chop pretag to the right length set pretag [string range $pretag 0 [expr $break_soft - $out_len]] # clip the last word regsub "\[^ \t\n\r]*$" $pretag {} pretag append out [string range $pretag 0 $break_soft] break } elseif { $nobr && [expr [string length $pretag] + $out_len] > $break_hard } { # we are in a nonbreaking tag and are past the hard break # so chop back to the point we got the nobr tag... set tagptr $nobr_tagptr if { $nobr_out_point > 0 } { set out [string range $out 0 [expr $nobr_out_point - 1]] } else { # here maybe we should decide if we should keep the tag anyway # if zero length result would be the result... set out {} } break } } # tack on pretag append out $pretag incr out_len [string length $pretag] } # now deal with the tag if we got one... if { $tag == "" } { # if the tag is empty we might have one of the bad matched that are not eating # any of the string so check for them if {[string length $match] == [string length $frag]} { append out $frag set frag {} } } else { set tag [string toupper $tag] if { ![info exists syn($tag)]} { # if we don't have an entry in our syntax table just tack it on # and hope for the best. if { ! $discard } { append out $fulltag } } else { if { $close != "/" } { # new tag # "remove" tags are just ignored here # discard tags if { $discard } { if { $syn($tag) == "discard" } { incr discard incr tagptr set tagstack($tagptr) $tag } } else { switch $syn($tag) { nobr { if { ! $nobr } { set nobr_out_point [string length $out] set nobr_tagptr $tagptr set nobr_len $out_len } incr nobr incr tagptr set tagstack($tagptr) $tag append out $fulltag } discard { incr discard incr tagptr set tagstack($tagptr) $tag } close { incr tagptr set tagstack($tagptr) $tag append out $fulltag } } } } else { # we got a close tag if { $discard } { # if we are in discard mode only watch for # closes to discarded tags if { $syn($tag) == "discard"} { if {$tagptr > -1} { if { $tag != $tagstack($tagptr) } { #puts "/$tag without $tag" } else { incr tagptr -1 incr discard -1 } } } } else { if { $syn($tag) != "remove"} { # if tag is a remove tag we just ignore it... if {$tagptr > -1} { if {$tag != $tagstack($tagptr) } { # puts "/$tag without $tag" } else { incr tagptr -1 if { $syn($tag) == "nobr"} { incr nobr -1 } append out $fulltag } } } } } } } } } # on exit of the look either we parsed it all or we truncated. # we should now walk the stack and close any open tags. for {set i $tagptr} { $i > -1 } {incr i -1} { # append out " " append out "" } return $out } ad_proc util_dbq { { -null_is_null_p f } vars } { Given a list of variable names this routine creates variables named DBQvariable_name which can be used in sql insert and update statements.

    If -null_is_null_p is t then we return the string "null" unquoted so that "update foo set var = $DBQvar where ..." will do what we want if we default var to "null". } { foreach var $vars { upvar 1 $var val if [info exists val] { if { $null_is_null_p == "t" && $val == {null} } { uplevel [list set DBQ$var {null}] } else { uplevel [list set DBQ$var "'[DoubleApos [string trim $val]]'"] } } } } proc_doc ad_decode { args } "this procedure is analogus to sql decode procedure. first parameter is the value we want to decode. this parameter is followed by a list of pairs where first element in the pair is convert from value and second element is convert to value. last value is default value, which will be returned in the case convert from values matches the given value to be decoded" { set num_args [llength $args] set input_value [lindex $args 0] set counter 1 while { $counter < [expr $num_args - 2] } { lappend from_list [lindex $args $counter] incr counter lappend to_list [lindex $args $counter] incr counter } set default_value [lindex $args $counter] if { $counter < 2 } { return $default_value } set index [lsearch -exact $from_list $input_value] if { $index < 0 } { return $default_value } else { return [lindex $to_list $index] } } proc_doc ad_urlencode { string } "same as ad_urlencode except that dash and underscore are left unencoded." { set encoded_string [ns_urlencode $string] regsub -all {%2d} $encoded_string {-} encoded_string regsub -all {%5f} $encoded_string {_} ad_encoded_string return $ad_encoded_string } ad_proc ad_get_cookie { { -include_set_cookies t } name { default "" } } { "Returns the value of a cookie, or $default if none exists." } { if { $include_set_cookies == "t" } { set headers [ns_conn outputheaders] for { set i 0 } { $i < [ns_set size $headers] } { incr i } { if { ![string compare [string tolower [ns_set key $headers $i]] "set-cookie"] && \ [regexp "^$name=(\[^;\]+)" [ns_set value $headers $i] "" "value"] } { return $value } } } set headers [ns_conn headers] set cookie [ns_set iget $headers Cookie] if { [regexp "$name=(\[^;\]+)" $cookie match value] } { return $value } return $default } ad_proc ad_set_cookie { { -replace f -secure f -expires "" -max_age "" -domain "" -path "/" } name value } { Sets a cookie. } { set headers [ns_conn outputheaders] if { $replace != "f" } { # Try to find an already-set cookie named $name. for { set i 0 } { $i < [ns_set size $headers] } { incr i } { if { ![string compare [string tolower [ns_set key $headers $i]] "set-cookie"] && \ [regexp "^$name=" [ns_set value $headers $i]] } { ns_set delete $headers $i break } } } set cookie "$name=$value" if { $path != "" } { append cookie "; Path=$path" } if { ![string compare [string tolower $expires] "never"] } { append cookie "; Expires=Fri, 01-Jan-2010 01:00:00 GMT" } elseif { $expires != "" } { append cookie "; Expires=$expires" } if { $max_age != "" } { append cookie "; Max-Age=$max_age" } if { $domain != "" } { append cookie "; Domain=$domain" } if { $secure != "f" } { append cookie "; Secure" } ns_set put $headers "Set-Cookie" $cookie } # Helper procedure for sortable_table. # column_list is a list of column names optionally followed by " desc". # Returns a new list with sort_column as the first element, followed # by the columns in column_list excluding any beginning with sort_column. proc sortable_table_new_sort_order {column_list sort_column} { set new_order [list $sort_column] # Relies on string representation of lists. [lindex "colname desc" 0] # returns just "colname". set just_the_sort_column [lindex $sort_column 0] foreach col $column_list { if { [lindex $col 0] != $just_the_sort_column } { lappend new_order $col } } return $new_order } proc_doc sortable_table {db select_string display_spec vars_to_export sort_var current_sort_order {table_length ""} {extra_table_parameters ""} {stripe_color_list ""} {max_results ""} {header_font_params ""} {row_font_params ""}} {Procedure to format a database query as a table that can be sorted by clicking on the headers. Arguments are:

    } { # Run the SQL set order_clause "" if { ![empty_string_p $current_sort_order] } { set order_clause " order by [join $current_sort_order ","]" } set selection [ns_db select $db "$select_string$order_clause"] # Start generating the table HTML. set table_start "\n" set table_html "" set primary_sort_column [lindex $current_sort_order 0] # Put in the headers. set headers "" foreach col_desc $display_spec { # skip any blank columns if { [llength $col_desc] < 1 } { continue } set primary_column_name [lindex $col_desc 0] # set the default sort order set primary_column_sort "" if { [llength $col_desc] > 3 } { set primary_column_sort "[lindex $col_desc 3]" } set column_header [lindex $col_desc 1] # Calculate the href for the header link. set this_url [ns_conn url] set exported_vars [export_ns_set_vars "url" $sort_var $vars_to_export] if { ![empty_string_p $exported_vars] } { append exported_vars "&" } set just_the_sort_column [lindex $primary_sort_column 0] set sort_icon "" if { $primary_column_name == $just_the_sort_column } { # This is the column that is being sorted on. Need to reverse # the direction of the sort by appending or removing " desc". # Relies on the fact that indexing past the end of a list # is not an error, just returns the empty string. # We're treating a string as a list here, since we know that # $primary_sort_column will be a plain column name, or a # column name followed by " desc". if { [lindex $primary_sort_column 1] == "desc" } { append exported_vars "$sort_var=[ns_urlencode [sortable_table_new_sort_order $current_sort_order $just_the_sort_column]]" set sort_icon "" } else { append exported_vars "$sort_var=[ns_urlencode [sortable_table_new_sort_order $current_sort_order "$just_the_sort_column desc"]]" set sort_icon "" } } else { # Clicked on some other column. append exported_vars "$sort_var=[ns_urlencode [sortable_table_new_sort_order $current_sort_order "$primary_column_name $primary_column_sort"]]" } if { [empty_string_p "[lindex $col_desc 4]"] } { append headers "" } append headers "\n" # Do the data rows. set i 0 set color_index 0 set n_colors [llength $stripe_color_list] set n_results 0 while { [ns_db getrow $db $selection] } { set_variables_after_query # check to see if we have reached our max results limit if { [exists_and_not_null max_results] } { if { $n_results >= $max_results } { break } incr n_results } # Handle table breaks. if { $i == 0 } { append table_html "$table_start$headers" } elseif { ![empty_string_p $table_length] } { if { $i % $table_length == 0 } { append table_html "
    " } else { append headers "" } append headers "$column_header$sort_icon
    \n$table_start$headers" set i 0 } } # Handle row striping. if { ![empty_string_p $stripe_color_list] } { append table_html "" set color_index [expr ($color_index + 1) % $n_colors] } else { append table_html "" } # Handle each display column. foreach col_desc $display_spec { # skip any blank columns if { [llength $col_desc] < 1 } { continue } set primary_column_name [lindex $col_desc 0] set col_display [lindex $col_desc 2] if { [empty_string_p $col_display] } { # Just use the sort column as the value. set col_display "\$$primary_column_name" } # Insert   for empty rows to avoid empty cells. set value [subst $col_display] if { [empty_string_p $value] } { set value " " } append table_html "$value" } append table_html "\n" incr i } ns_db flush $db if { ![empty_string_p $table_html] } { append table_html "" } return $table_html } proc ad_handle_filter { conn why } { foreach f [nsv_get ad_filters "[ns_conn method],$why" ] { if { [string match [lindex $f 3] [ns_conn url]] } { set errno [catch { set proc [lindex $f 4] set args [lindex $f 5] set debug [lindex $f 6] set proc_args [info args $proc] set proc_argcount [llength $proc_args] if { [lindex $proc_args [expr { [llength $proc_args] - 2 }]] == "args" } { set args [list $args] } set actual_argcount [llength $args] if { $debug == "t" } { ns_log "Notice" "Executing filter $proc for [ns_conn method] [ns_conn url]..." } if { $actual_argcount >= 3 || $proc_argcount - $actual_argcount == 2 } { # Procedure has conn and why. set result [eval $proc [concat [list $conn] $args [list $why]]] } elseif { $proc_argcount - $actual_argcount == 1 } { # Procedure has why. set result [eval $proc [concat $args [list $why]]] } else { set result [eval $proc $args] } if { $debug == "t" } { ns_log "Notice" "Done executing filter $proc." } if { $result == "filter_break" } { set return "filter_break" } elseif { $result == "filter_return" } { set return "filter_return" } elseif { $result != "filter_ok" } { ns_log "Filter" "Invalid result \"$result\" from filter $proc: should be filter_ok, filter_break, or filter_return" if { [lindex $f 7] == "t" } { error "Critical filter $proc failed." } } } errmsg] if { $errno } { ns_log "Error" "Filter $proc returned error #$errno: $errmsg" if { [lindex $f 7] == "t" } { error "Critical filter $proc failed." } } } if { [info exists return] } { return $return } } return "filter_ok" } # Make sure the ad_filters array exists nsv_set ad_filters . "" ad_proc ad_register_filter { { -debug f -priority 10000 -critical f } kind method path proc args } { Registers a filter (see ns_register_filter for syntax). Priority is an integer; lower numbers indicate higher priority. Use a method of "*" to register GET, POST, and HEAD filters. If a filter is not critical, page viewing will not abort if a filter fails. If debug is set to "t", all invocations of the filter will be ns_logged. } { if { $method == "*" } { # Shortcut to allow registering filter for all methods. foreach method { GET POST HEAD } { eval [concat [list ad_register_filter -debug $debug -priority $priority -critical $critical $kind $method $path $proc] $args] } return } ns_mutex lock [nsv_get ad_filters mutex] # Append the filter to our list. set filters [nsv_get ad_filters "$method,$kind"] set filter_info [list $priority $kind $method $path $proc $args $debug $critical] set counter 0 # Insert the filter in sorted order (lowest priority# first). foreach f $filters { if { ![string compare $f $filter_info] } { ns_log "Notice" "$kind filter $proc already registered for $method $path" ns_mutex unlock [nsv_get ad_filters mutex] return } if { $priority < [lindex $f 0] } { break } incr counter } ns_log "Notice" "Registering $kind filter $proc for $method $path with priority $priority" set filters [linsert $filters $counter $filter_info] nsv_set ad_filters "$method,$kind" $filters ns_mutex unlock [nsv_get ad_filters mutex] } proc_doc ad_run_scheduled_proc { proc_info } { Runs a scheduled procedure and updates monitoring information in the shared variables. } { # Grab information about the scheduled procedure. set thread [lindex $proc_info 0] set once [lindex $proc_info 1] set interval [lindex $proc_info 2] set proc [lindex $proc_info 3] set args [lindex $proc_info 4] set time [lindex $proc_info 5] set count 0 set debug [lindex $proc_info 7] ns_mutex lock [nsv_get ad_procs mutex] set procs [nsv_get ad_procs .] # Find the entry in the shared variable. Splice it out. for { set i 0 } { $i < [llength $procs] } { incr i } { set other_proc_info [lindex $procs $i] for { set j 0 } { $j < 5 } { incr j } { if { [lindex $proc_info $j] != [lindex $other_proc_info $j] } { break } } if { $j == 5 } { set count [lindex $other_proc_info 6] set procs [lreplace $procs $i $i] break } } if { $once == "f" } { # The proc will run again - readd it to the shared variable (updating ns_time and # incrementing the count). lappend procs [list $thread $once $interval $proc $args [ns_time] [expr { $count + 1 }] $debug] } nsv_set ad_procs . $procs ns_mutex unlock [nsv_get ad_procs mutex] if { $debug == "t" } { ns_log "Notice" "Running scheduled proc $proc..." } # Actually run the procedure. eval [concat [list $proc] $args] if { $debug == "t" } { ns_log "Notice" "Done running scheduled proc $proc." } } ad_proc ad_schedule_proc { { -thread f -once f -debug t } interval proc args } { Replacement for ns_schedule_proc, allowing us to track what's going on. Can be monitored via /admin/monitoring/schedule-procs.tcl. } { # Protect the list of scheduled procs with a mutex. ns_mutex lock [nsv_get ad_procs mutex] set proc_info [list $thread $once $interval $proc $args [ns_time] 0 $debug] ns_log "Notice" "Scheduling proc $proc" # Add to the list of scheduled procedures, for monitoring. set procs [nsv_get ad_procs .] lappend procs $proc_info nsv_set ad_procs . $procs ns_mutex unlock [nsv_get ad_procs mutex] set my_args [list] if { $thread == "t" } { lappend my_args "-thread" } if { $once == "t" } { lappend my_args "-once" } # Schedule the wrapper procedure (ad_run_scheduled_proc). eval [concat [list ns_schedule_proc] $my_args [list $interval ad_run_scheduled_proc [list $proc_info]]] } if { ![nsv_exists ad_filters mutex] } { nsv_set ad_filters mutex [ns_mutex create] foreach method { GET POST HEAD } { foreach kind { preauth postauth trace } { ns_log "Notice" "Setting up $kind filter for \"$method\" method" nsv_set ad_filters "$method,$kind" "" ns_register_filter $kind $method /* ad_handle_filter } } nsv_set ad_procs mutex [ns_mutex create] nsv_set ad_procs . "" } util_report_successful_library_load