ad_library { Provides a variety of non-ACS-specific utilities, including the procs to support the who's online feature. @author Various (acs@arsdigita.com) @creation-date 13 April 2000 @cvs-id $Id: utilities-procs.tcl,v 1.189.2.5 2019/03/10 21:34:33 gustafn Exp $ } namespace eval util {} ad_proc util::pdfinfo { file } { Calls the pdfinfo command line utility on a given pdf file. The command pdfinfo must be installed on the server for this to work. On linux this is usually part of the poppler-utils (https://poppler.freedesktop.org/). @param file absolute path to the pdf file @return a dict containing all the pdfinfo returned fields as keys and their respective values } { set pdfinfo [util::which pdfinfo] if {$pdfinfo eq ""} { error "the command 'pdfinfo' is not found on the system" } set retval [dict create] foreach line [split [exec $pdfinfo $file] \n] { lassign [split $line ":"] name value set name [string trim $name] set value [string trim $value] dict set retval $name $value } return $retval } ad_proc util::zip { -source:required -destination:required } { Create a zip file. @param source is the content to be zipped. If it is a directory, archive will contain all files into directory without the trailing directory itself. @param destination is the name of the created file } { # # Split the source # if {[file isfile $source]} { set filename [file tail $source] set in_path [file dirname $source] } else { set filename "." set in_path $source } # # Check if zipfile::mkzip, introduced in tcllib 1.18, is available. # Otherwise, use the legacy method calling an external zip command via exec. # if {![catch {package require zipfile::mkzip} version]} { ::zipfile::mkzip::mkzip $destination -directory $in_path $filename } else { set zip [util::which zip] if {$zip eq ""} { error "zip command not found on the system." } # # To avoid having the full path of the file included in the archive, # we must first cd to the source directory. zip doesn't have an option # to do this without building a little script... # set cmd [list exec] switch -- $::tcl_platform(platform) { windows { lappend cmd cmd.exe /c set zip_cmd [list] lappend zip_cmd "cd $in_path" lappend zip_cmd "${zip} -r \"${destination}\" \"${filename}\"" set zip_cmd [join $zip_cmd " && "] lappend cmd $zip_cmd } default { # # Previous versions of this, for unix-like systems, used bash in # order to change directories before executing zip (see above). # # This method was problematic when using certain characters for # the filenames, such as backticks, for example. # # In order to avoid this and properly quote everything, we use # tclsh instead, in a convoluted and funny way. # # (Thanks to Nathan Coulter for the hack.) # # TODO: test this also on windows. It may work as well, and # potentially unify the two legacy implementations. # set tcl_shell [util::which tclsh] if {$tcl_shell eq ""} { error "tclsh command not found on the system." } lappend cmd $tcl_shell - set script [ string map [ list @in_path@ [list $in_path] @zip@ [list $zip] @destination@ [list $destination] @filename@ [list $filename] ] { if { [catch { cd @in_path@ exec @zip@ -r @destination@ @filename@ } errorMsg eopts] } { puts "Error: [dict get $eopts -errorinfo]" exit 1 } } ] lappend cmd << $script } } # Create the archive {*}$cmd } } ad_proc util::unzip { -source:required -destination:required -overwrite:boolean } { @param source must be the name of a valid zip file to be decompressed @param destination must be the name of a valid directory to contain decompressed files } { set unzip [util::which unzip] if {$unzip eq ""} {error "unzip command not found on the system."} # -n means we don't overwrite existing files set cmd [list exec $unzip] if {$overwrite_p} {lappend cmd -o } else {lappend cmd -n} lappend cmd $source -d $destination {*}$cmd } # 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. 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 } } ad_proc 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 { $extra_message eq "" } { set message "Loading $scrubbed_path" } else { set message "Loading $scrubbed_path; $extra_message" } ns_log Notice $message } ad_proc check_for_form_variable_naughtiness { name value } { 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... 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. Checks the given variable for against known form variable exploits. If it finds anything objectionable, it throws an error. } { # security patch contributed by michael@cleverly.com if { [string match "QQ*" $name] } { error "Form variables should never begin with QQ!" } # contributed by michael@cleverly.com if { "Vform_counter_i" eq $name } { error "Vform_counter_i not an allowed form variable" } # The statements below make ACS more secure, because it prevents # overwrite of variables from something like set_the_usual_form_variables # and it will be better if it was in the system. Yet, it is commented # out because it will cause an unstable release. To add this security # feature, we will need to go through all the code in the ACS and make # sure that the code doesn't try to overwrite intentionally and also # check to make sure that when Tcl files are sourced from another proc, # the appropriate variables are unset. If you want to install this # security feature, then you can look in the release notes for more info. # # security patch contributed by michael@cleverly.com, # fixed by iwashima@arsdigita.com # # upvar 1 $name name_before # if { [info exists name_before] } { # The variable was set before the proc was called, and the # form attempts to overwrite it # error "Setting the variables from the form attempted to overwrite existing variable $name" # } # no naughtiness with uploaded files (discovered by ben@mit.edu) # patch by richardl@arsdigita.com, with no thanks to # jsc@arsdigita.com. if { [string match "*tmpfile" $name] } { set tmp_filename [ns_queryget $name] # ensure no .. in the path ns_normalizepath $tmp_filename set passed_check_p 0 # check to make sure path is to an authorized directory set tmpdir_list [ad_parameter_all_values_as_list -package_id [ad_conn subsite_id] TmpDir] if { $tmpdir_list eq "" } { set tmpdir_list [list [ns_config ns/parameters tmpdir] "/var/tmp" "/tmp"] } foreach tmpdir $tmpdir_list { if { [string match "$tmpdir*" $tmp_filename] } { set passed_check_p 1 break } } if { !$passed_check_p } { error "You specified a path to a file that is not allowed on the system!" } } # integrates with the ad_set_typed_form_variable_filter system # written by dvr@arsdigita.com # see if this is one of the typed variables global ad_typed_form_variables if { [info exists ad_typed_form_variables] } { foreach typed_var_spec $ad_typed_form_variables { set typed_var_name [lindex $typed_var_spec 0] if { ![string match $typed_var_name $name] } { # no match. Go to the next variable in the list continue } # the variable matched the pattern set typed_var_type [lindex $typed_var_spec 1] if { "" eq $typed_var_type } { # if they don't specify a type, the default is 'integer' set typed_var_type integer } set variable_safe_p [ad_var_type_check_${typed_var_type}_p $value] if { !$variable_safe_p } { ns_returnerror 500 "variable $name failed '$typed_var_type' type check" ns_log Error "check_for_form_variable_naughtiness: [ad_conn url] called with \$$name = $value" error "variable $name failed '$typed_var_type' type check" ad_script_abort } # we've found the first element in the list that matches, # and we don't want to check against any others break } } } ad_proc -deprecated DoubleApos {string} { When the value "O'Malley" is inserted int an SQL database, the single quote can cause troubles in SQL, one has to insert 'O''Malley' instead.
In general, one should be using bind variables rather than
calling DoubleApos.
@return string with single quotes converted to a pair of single quotes
} {
set result [ns_dbquotevalue $string]
# remove the leading quote if necessary
if {[string range $result 0 0] eq '} {
set result [string range $result 1 end-1]
}
return $result
}
# debugging kludges
ad_proc -public NsSettoTclString {set_id} {
returns a plain text version of the passed ns_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
}
ad_proc -public get_referrer {-relative:boolean} {
@return referer from the request headers.
@param relative return the refer without protocol and host
} {
set url [ns_set get [ns_conn headers] Referer]
if {$relative_p} {
# In case the referrer URL has a protocol and host remove it
regexp {^[a-z]+://[^/]+(/.*)$} $url . url
}
return $url
}
##
# Database-related code
##
ad_proc -public 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}
set trimmed_month [string trimleft $month 0]
set pretty_month [lindex $allthemonths $trimmed_month-1]
set trimmed_day [string trimleft $day 0]
return "$pretty_month $trimmed_day, $year"
}
}
ad_proc -public remove_nulls_from_ns_set {
old_set_id
} {
Creates and returns a new ns_set without any null value fields
@return new ns_set
} {
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] ne "" } {
ns_set put $new_set_id [ns_set key $old_set_id $i] [ns_set value $old_set_id $i]
}
}
return $new_set_id
}
ad_proc -public merge_form_with_query {
{-bind {}}
form statement_name sql_qry
} {
Merges a form with a query string.
@param form the form to be stuffed.
@param statement_name An identifier for the sql_qry to be executed.
@param sql_qry The sql that must be executed.
@param bind A ns_set stuffed with bind variables for the sql_qry.
} {
set set_id [ns_set create]
ns_log debug "merge_form_with_query: statement_name = $statement_name"
ns_log debug "merge_form_with_query: sql_qry = $sql_qry"
ns_log debug "merge_form_with_query: set_id = $set_id"
db_0or1row $statement_name $sql_qry -bind $bind -column_set set_id
if { $set_id ne "" } {
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
}
ad_proc util_PrettyTclBoolean {
zero_or_one
} {
Turns a 1 (or anything else that makes a Tcl IF happy) into Yes; anything else into No
} {
if {$zero_or_one} {
return "Yes"
} else {
return "No"
}
}
ad_proc -public randomInit {seed} {
seed the random number generator.
} {
nsv_set rand ia 9301
nsv_set rand ic 49297
nsv_set rand im 233280
nsv_set rand seed $seed
}
ad_proc -public random {} {
Return a pseudo-random number between 0 and 1.
} {
nsv_set rand seed [expr {([nsv_get rand seed] * [nsv_get rand ia] + [nsv_get rand ic]) % [nsv_get rand im]}]
return [expr {[nsv_get rand seed]/double([nsv_get rand im])}]
}
ad_proc -public randomRange {range} {
Returns a pseudo-random number between 0 and range.
@return integer
} {
incr range
return [expr {int([random] * $range) % $range}]
}
ad_proc -public db_html_select_options {
{ -bind "" }
{ -select_option "" }
stmt_name
sql
} {
Generate html option tags for an HTML selection widget. If select_option
is passed, this option will be marked as selected.
@author yon [yon@arsdigita.com]
} {
set select_options ""
if { $bind ne "" } {
set options [db_list $stmt_name $sql -bind $bind]
} else {
set options [db_list $stmt_name $sql]
}
foreach option $options {
if { $option eq $select_option } {
append select_options "\n"
} else {
append select_options "\n"
}
}
return $select_options
}
ad_proc -public db_html_select_value_options {
{ -bind "" }
{ -select_option "" }
{ -value_index 0 }
{ -option_index 1 }
stmt_name
sql
} {
Generate html option tags with values for an HTML selection widget. If
select_option is passed and there exists a value for it in the values
list, this option will be marked as selected. The "select_option" can be
a list, in which case all options matching a value in the list will be
marked as selected.
@author yon [yon@arsdigita.com]
} {
set select_options ""
if { $bind ne "" } {
set options [db_list_of_lists $stmt_name $sql -bind $bind]
} else {
set options [uplevel [list db_list_of_lists $stmt_name $sql]]
}
foreach option $options {
if { [lindex $option $value_index] in $select_option } {
append select_options "\n"
} else {
append select_options "\n"
}
}
return $select_options
}
#####
#
# Export Procs
#
#####
ad_proc -public export_vars {
-sign:boolean
-form:boolean
-url:boolean
-quotehtml:boolean
-entire_form:boolean
-no_empty:boolean
{-base}
-no_base_encode:boolean
{-anchor}
{-exclude {}}
{-override {}}
{vars {}}
} {
Exports variables either in URL or hidden form variable format. It should replace
export_form_vars
,
export_url_vars
and all their friends.
Example usage: [export_vars -form { foo bar baz }]
This will export the three variables foo
, bar
and baz
as
hidden HTML form fields. It does exactly the same as [export_vars -form {foo bar baz}]
.
Example usage: [export_vars -sign -override {{foo "new value"}} -exclude { bar } { foo bar baz }]
This will export a variable named foo
with the value "new value" and a variable named baz
with the value of baz
in the caller's environment. Since we've specified that bar
should be
excluded, bar
won't get exported even though it's specified in the last argument. Additionally, even though
foo
is specified also in the last argument, the value we use is the one given in the override
argument. Finally, both variables are signed, because we specified the -sign
switch.
You can specify variables with three different precedences, namely
override
, exclude
or vars
. If a variable is present in override
,
that's what'll get exported, no matter what. If a variable is in exclude
and not in override
,
then it will not get output. However, if it is in vars
and not in either of
override
or exclude
, then it'll get output. In other words, we check override
,
exclude
and vars
in that order of precedence.
The two variable specs, vars
and override
both look the same: They take a list of
variable specs. Examples of variable specs are:
subst
on it, so backslashes, square brackets and variables will get substituted correctly. Therefore, make sure you use
curly braces to surround this instead of the [list]
command; otherwise the contents will get substituted
twice, and you'll be in trouble.
Right after the name, you may specify a colon and some flags, separated by commas. Valid flags are:
:array
flag of
ad_page_contract
, which means
that each entry will get output as name.key=value
.
If you don't specify a value directly, but want it pulled out of the Tcl environment, then you don't
need to specify :array
. If you do, and the variable is in fact not an array, an error will
be thrown.
:verify
flag of
ad_page_contract
and
makes sure that the value isn't tampered with on the client side. The -sign
switch to export_vars
, is a short-hand for specifying the :sign
switch
on every variable.
For example, one can use "user_id:sign(max_age=60)" in export_vars to let the exported variable after 60 seconds. Other potential arguments for sign are "user" or "csrf" to bind the signature to a user or to the CSRF token.
exclude
simply takes a list of names of variables that you don't
want exported, even though they're specified in vars
.
Intended use: A page may have a set of variables that it cares about. You can store this in
a variable once and pass that to export_vars
like this:
set my_vars { user_id sort_by filter_by }
... [export_vars $my_vars] ...
Then, say one of them contains a column to filter on. When you want to clear that column, you can say
[export_vars -exclude { filter_by } $my_vars]
.
Similarly, if you want to change the sort order, you can say
[export_vars -override { { sort_by $column } } $my_vars]
, and sorting will be done according to
the new value of column
.
If the variable name contains a colon (:), that colon must be escaped with a backslash,
so for example "form:id" becomes "form\:id". Sorry.
@param sign Sign all variables.
@param url Export in URL format. This is the default.
@param form Export in form format. You can't specify both URL and form format.
@param quotehtml HTML quote the entire resulting string. This is an interim solution
while we're waiting for the templating system to do the quoting for us.
@param entire_form Export the entire form from the GET query string or the POST.
@option no_empty If specified, variables with an empty string value will be suppressed from being exported.
This avoids cluttering up the URLs with lots of unnecessary variables.
@option base The base URL to make a link to. This will be prepended to the query string
along with a question mark (?), if the query is non-empty. So the returned
string can be used directly in a link. This is only relevant to URL export.
@option no_base_encode Decides whether argument passed as base
option will be
encoded by ad_urlencode_url proc
@author Lars Pind (lars@pinds.com)
@creation-date December 7, 2000
} {
if { $form_p && $url_p } {
return -code error "You must select either form format or url format, not both."
}
# default to URL format
if { !$form_p && !$url_p } {
set url_p 1
}
#
# TODO: At least the parsing of the options should be transformed
# to produce a single dict, containing the properties of all form
# vars (probably optionally) and specified arguments. The dict
# should be the straightforward source for the genertion of the
# output set. One should be able to speed the code significantly
# up (at least for the standard cases).
#
# -Gustaf Neumann
#
# 'noprocessing_vars' is yet another container of variables,
# only this one doesn't have the values subst'ed
# and we don't try to find :multiple and :array flags in the namespec
set noprocessing_vars [list]
if { $entire_form_p } {
set the_form [ns_getform]
if { $the_form ne "" } {
for { set i 0 } { $i < [ns_set size $the_form] } { incr i } {
set varname [ns_set key $the_form $i]
set varvalue [ns_set value $the_form $i]
lappend noprocessing_vars [list $varname $varvalue]
}
}
}
#####
#
# Parse the arguments
#
#####
# 1. if they're in override, use those
# 2. if they're in vars, but not in exclude or override, use those
# There'll always be an entry here if the variable is to be exported
array set exp_precedence_type [list]
# This contains entries of the form exp_flag(name:flag) e.g., exp_flag(foo:multiple)
array set exp_flag [list]
# This contains the value if provided, otherwise we'll pull it out of the caller's environment
array set exp_value [list]
foreach precedence_type { override exclude vars noprocessing_vars } {
foreach var_spec [set $precedence_type] {
if { [llength $var_spec] > 2 } {
return -code error "A varspec must have either one or two elements."
}
if { $precedence_type ne "noprocessing_vars" } {
# Hide escaped colons for below split
regsub -all {\\:} $var_spec "!!cOlOn!!" var_spec
set name_spec [split [lindex $var_spec 0] ":"]
# Replace escaped colons with single colon
regsub -all {!!cOlOn!!} $name_spec ":" name_spec
set name [lindex $name_spec 0]
} else {
set name [lindex $var_spec 0]
# Nothing after the colon, since we don't interpret any colons
set name_spec [list $name {}]
}
# If we've already encountered this varname, ignore it
if { ![info exists exp_precedence_type($name)] } {
set exp_precedence_type($name) $precedence_type
if { $precedence_type ne "exclude" } {
foreach flag [split [lindex $name_spec 1] ","] {
set exp_flag($name:$flag) 0
if {[regexp {^(\w+)[\(](.+)[\)]$} $flag . flag value]} {
set exp_flag($name:$flag) $value
}
}
if { $sign_p } {
set exp_flag($name:sign) ""
}
if { [llength $var_spec] > 1 } {
if { $precedence_type ne "noprocessing_vars" } {
set value [uplevel subst \{[lindex $var_spec 1]\}]
} else {
set value [lindex $var_spec 1]
}
set exp_value($name) $value
# If the value is specified explicitly, we include it even if the value is empty
} else {
upvar 1 $name upvar_variable
if { [info exists upvar_variable] } {
if { [array exists upvar_variable] } {
if { $no_empty_p } {
# If the no_empty_p flag is set, remove empty string values first
set exp_value($name) [list]
foreach { key value } [array get upvar_variable] {
if { $value ne "" } {
lappend exp_value($name) $key $value
}
}
} else {
# If no_empty_p isn't set, just do an array get
set exp_value($name) [array get upvar_variable]
}
set exp_flag($name:array) 0
} else {
if { [info exists exp_flag($name:array)] } {
return -code error "Variable \"$name\" is not an array"
}
if { !$no_empty_p } {
set exp_value($name) $upvar_variable
} else {
# no_empty_p flag set, remove empty strings
if { [info exists exp_flag($name:multiple)] } {
# This is a list, remove empty entries
set exp_value($name) [list]
foreach elm $upvar_variable {
if { $elm ne "" } {
lappend exp_value($name) $elm
}
}
} else {
# Simple value, this is easy
if { $upvar_variable ne "" } {
set exp_value($name) $upvar_variable
}
}
}
}
}
}
}
}
}
}
#####
#
# Put the variables into the export_set
#
#####
# We use an ns_set, because there may be more than one entry with the same name
set export_set [ns_set create]
foreach name [array names exp_precedence_type] {
if { $exp_precedence_type($name) ne "exclude" } {
if { [info exists exp_value($name)] } {
if { [info exists exp_flag($name:array)] } {
if { [info exists exp_flag($name:multiple)] } {
foreach { key value } $exp_value($name) {
foreach item $value {
ns_set put $export_set "${name}.${key}" $item
}
}
} else {
foreach { key value } $exp_value($name) {
ns_set put $export_set "${name}.${key}" $value
}
}
if { [info exists exp_flag($name:sign)] } {
# DRB: array get does not define the order in which elements are returned,
# meaning that arrays constructed in different ways can have different
# signatures unless we sort the returned list. I ran into this the
# very first time I tried to sign an array passed to a page that used
# ad_page_contract to verify the veracity of the parameter.
ns_set put $export_set "$name:sig" \
[export_vars_sign -params $exp_flag($name:sign) [lsort $exp_value($name)]]
}
} else {
if { [info exists exp_flag($name:multiple)] } {
foreach item $exp_value($name) {
ns_set put $export_set $name $item
}
} else {
ns_set put $export_set $name "$exp_value($name)"
}
if { [info exists exp_flag($name:sign)] } {
ns_set put $export_set "$name:sig" \
[export_vars_sign -params $exp_flag($name:sign) $exp_value($name)]
}
}
}
}
}
#####
#
# Translate it into the appropriate format
#
#####
set export_size [ns_set size $export_set]
set export_string {}
if { $url_p } {
set export_list [list]
for { set i 0 } { $i < $export_size } { incr i } {
lappend export_list [ad_urlencode_query [ns_set key $export_set $i]]=[ad_urlencode_query [ns_set value $export_set $i]]
}
set export_string [join $export_list "&"]
} else {
for { set i 0 } { $i < $export_size } { incr i } {
append export_string [subst {
This proc is a replacement for ns_returnredirect, but improved in two important respects:
We can't just use [file dirname [ad_conn url]] because we want /foo/bar/ to return /foo/bar/ and not /foo .
Also, we want to return directory WITH the trailing slash
so that programs that use this proc don't have to treat
the root directory as a special case.
} {
set path [ad_conn vhost_url]
set lastchar [string index $path end]
if {$lastchar eq "/" } {
return $path
} else {
set file_dirname [file dirname $path]
# Treat the case of the root directory special
if {$file_dirname eq "/" } {
return /
} else {
return $file_dirname/
}
}
}
ad_proc -public ad_call_proc_if_exists { proc args } {
Calls a procedure with particular arguments, only if the procedure is defined.
} {
if { [info commands $proc] ne "" } {
$proc {*}$args
}
}
ad_proc -public ad_get_tcl_call_stack {
{level -2}
} {
Returns a stack trace from where the caller was called. See also
ad_print_stack_trace which generates a more readable stack trace
at the expense of truncating args.
@param level The level to start from, relative to this
proc. Defaults to -2, meaning the proc that called this proc's
caller. Per default, don't show "ad_log", when this calls
ad_get_tcl_call_stack.
@author Lars Pind (lars@pinds.com)
@see ad_print_stack_trace
} {
set stack ""
#
# keep the previous state of ::errorInfo
#
set errorInfo $::errorInfo
for { set x [expr {[info level] + $level}] } { $x > 0 } { incr x -1 } {
set info [info level $x]
regsub -all \n $info {\\n} info
#
# In case, we have an nsf frame, add information about the
# current object and the current class to the debug output.
#
if {![catch {uplevel #$x ::nsf::current} obj]
&& ![catch {uplevel #$x [list ::nsf::current class]} class]
} {
set objInfo [list $obj $class]
set info "{$objInfo} $info"
}
#
# Don't produce too long lines
#
if {[string length $info]>200} {
set arglist ""
foreach arg $info {
if {[string length $arg]>40} {set arg [string range $arg 0 40]...}
lappend arglist $arg
}
set info $arglist
}
append stack " called from $info\n"
}
#
# restore previous state of ::errorInfo
#
set ::errorInfo $errorInfo
return $stack
}
ad_proc -public ad_ns_set_to_tcl_vars {
{-duplicates overwrite}
{-level 1}
set_id
} {
Takes an ns_set and sets variables in the caller's environment
correspondingly, i.e. if key is foo and value is bar, the Tcl var
foo is set to bar.
@param duplicates This optional switch argument defines what happens if the
Tcl var already exists, or if there are duplicate entries for the same key.
overwrites
just overwrites the var, which amounts to letting the
ns_set win over pre-defined vars, and later entries in the ns_set win over
earlier ones. ignore
means the variable isn't overwritten.
fail
will make this proc fail with an error. This makes it
easier to track subtle errors that could occur because of unpredicted name
clashes.
@param level The level to upvar to.
@author Lars Pind (lars@pinds.com)
} {
if { $duplicates ni {ignore fail overwrite} } {
return -code error "The optional switch duplicates must be either overwrite, ignore or fail"
}
set size [ns_set size $set_id]
for { set i 0 } { $i < $size } { incr i } {
set varname [ns_set key $set_id $i]
upvar $level $varname var
if { [info exists var] } {
switch -- $duplicates {
fail {
return -code error "ad_ns_set_to_tcl_vars tried to set the var $varname which is already set"
}
ignore {
# it's already set ... don't overwrite it
continue
}
}
}
set var [ns_set value $set_id $i]
}
}
ad_proc -public ad_tcl_vars_to_ns_set {
-set_id
-put:boolean
args
} {
Takes a list of variable names and ns_set update
s values in an ns_set
correspondingly: key is the name of the var, value is the value of
the var. The caller is (obviously) responsible for freeing the set if need be.
@param set_id If this switch is specified, it'll use this set instead of
creating a new one.
@param put If this boolean switch is specified, it'll use ns_set put
instead
of ns_set update
(update is default)
@param args A number of variable names that will be transported into the ns_set.
@author Lars Pind (lars@pinds.com)
} {
if { ![info exists set_id] } {
set set_id [ns_set create]
}
if { $put_p } {
set command put
} else {
set command update
}
foreach varname $args {
upvar $varname var
ns_set $command $set_id $varname $var
}
return $set_id
}
ad_proc -public ad_tcl_vars_list_to_ns_set {
-set_id
-put:boolean
vars_list
} {
Takes a Tcl list of variable names and ns_set update
s values in an ns_set
correspondingly: key is the name of the var, value is the value of
the var. The caller is (obviously) responsible for freeing the set if need be.
@param set_id If this switch is specified, it'll use this set instead of
creating a new one.
@param put If this boolean switch is specified, it'll use ns_set put
instead
of ns_set update
(update is default)
@param vars_list A Tcl list of variable names that will be transported into the ns_set.
@author Lars Pind (lars@pinds.com)
} {
if { ![info exists set_id] } {
set set_id [ns_set create]
}
if { $put_p } {
set command put
} else {
set command update
}
foreach varname $vars_list {
upvar $varname var
ns_set $command $set_id $varname $var
}
return $set_id
}
ad_proc -public util_sets_equal_p { list1 list2 } {
Tests whether each unique string in list1 occurs as many
times in list1 as in list2 and vice versa (regardless of order).
@return 1 if the lists have identical sets and 0 otherwise
@author Peter Marklund
} {
return [expr {[llength $list1] == [llength $list2] &&
[lsort $list1] eq [lsort $list2]}]
}
ad_proc -public util_subset_p {
list1
list2
} {
Tests whether list1 is a subset of list2.
@return 1 if list1 is a subset of list2.
@author Peter Marklund
} {
if { [llength $list1] == 0 } {
# The empty list is always a subset of any list
return 1
}
set sorted_list1 [lsort $list1]
set sorted_list2 [lsort $list2]
set len1 [llength $sorted_list1]
set len2 [llength $sorted_list2]
# Loop over list1 and list2 in sort order, comparing the elements
set index1 0
set index2 0
while { $index1 < $len1 && $index2 < $len2 } {
set elm1 [lindex $sorted_list1 $index1]
set elm2 [lindex $sorted_list2 $index2]
set compare [string compare $elm1 $elm2]
switch -exact -- $compare {
-1 {
# elm1 < elm2
# The first element in list1 is smaller than any element in list2,
# therefore this element cannot exist in list2, and therefore list1 is not a subset of list2
return 0
}
0 {
# A match, great, next element
incr index1
incr index2
continue
}
1 {
# elm1 > elm2
# Move to the next element in list2, knowing that this will be larger, and therefore
# potentially equal to the element in list1
incr index2
}
}
}
if { $index1 == $len1 } {
# We've reached the end of list1, finding all elements along the way, we're done
return 1
} else {
# One or more elements in list1 not found in list2
return 0
}
}
ad_proc -public util_get_subset_missing {
list1
list2
} {
Returns the elements in list1 that are not in list2. Ignores duplicates.
@return The list of elements from list1 that could not be found in list2.
@author Peter Marklund
} {
if { [llength $list1] == 0 } {
# The empty list is always a subset of any list
return [list]
}
set sorted_list1 [list]
foreach elm [lsort $list1] {
if { [llength $sorted_list1] == 0 || [lindex $sorted_list1 end] ne $elm } {
lappend sorted_list1 $elm
}
}
set sorted_list2 [lsort $list2]
set len1 [llength $sorted_list1]
set len2 [llength $sorted_list2]
set missing_elms [list]
# Loop over list1 and list2 in sort order, comparing the elements
set index1 0
set index2 0
while { $index1 < $len1 && $index2 < $len2 } {
set elm1 [lindex $sorted_list1 $index1]
set elm2 [lindex $sorted_list2 $index2]
set compare [string compare $elm1 $elm2]
switch -exact -- $compare {
-1 {
# elm1 < elm2
# The first element in list1 is smaller than any element in list2,
# therefore this element cannot exist in list2, and therefore list1 is not a subset of list2
lappend missing_elms $elm1
incr index1
}
0 {
# A match, great, next element
incr index1
incr index2
continue
}
1 {
# elm1 > elm2
# Move to the next element in list2, knowing that this will be larger, and therefore
# potentially equal to the element in list1
incr index2
}
}
}
if { $index1 == $len1 } {
# We've reached the end of list1, finding all elements along the way, we're done
return $missing_elms
} else {
# One or more elements in list1 not found in list2
return [concat $missing_elms [lrange $sorted_list1 $index1 end]]
}
}
ad_proc -public ad_tcl_list_list_to_ns_set {
-set_id
-put:boolean
kv_pairs
} {
Takes a list of lists of key/value pairs and ns_set update
s
values in an ns_set.
@param set_id If this switch is specified, it'll use this set instead of
creating a new one.
@param put If this boolean switch is specified, it'll use
ns_set put
instead of ns_set update
(update is default)
@param kv_pairs A list of lists containing key/value pairs to be stuffed into
the ns_set
@author Yonatan Feldman (yon@arsdigita.com)
} {
if { ![info exists set_id] } {
set set_id [ns_set create]
}
if { $put_p } {
set command put
} else {
set command update
}
foreach kv_pair $kv_pairs {
ns_set $command $set_id [lindex $kv_pair 0] [lindex $kv_pair 1]
}
return $set_id
}
ad_proc -public ad_ns_set_keys {
-colon:boolean
{-exclude ""}
set_id
} {
Returns the keys of a ns_set as a Tcl list, like array names
.
@param colon If set, will prepend all the keys with a colon; useful for bind variables
@param exclude Optional Tcl list of key names to exclude
@author Lars Pind (lars@pinds.com)
} {
set keys [list]
set size [ns_set size $set_id]
for { set i 0 } { $i < $size } { incr i } {
set key [ns_set key $set_id $i]
if {$key ni $exclude} {
if { $colon_p } {
lappend keys ":$key"
} else {
lappend keys $key
}
}
}
return $keys
}
ad_proc -public util_wrap_list {
{ -eol " \\" }
{ -indent 4 }
{ -length 70 }
items
} {
Wraps text to a particular line length.
@param eol the string to be used at the end of each line.
@param indent the number of spaces to use to indent all lines after the
first.
@param length the maximum line length.
@param items the list of items to be wrapped. Items are
HTML-formatted. An individual item will never be wrapped onto separate
lines.
} {
set out "
" set line_length 0 set line_number 0 foreach item $items { regsub -all {<[^>]+>} $item "" item_notags if { $line_length > $indent } { if { $line_length + 1 + [string length $item_notags] > $length } { append out "$eol\n" incr line_number for { set i 0 } { $i < $indent } { incr i } { append out " " } set line_length $indent } else { append out " " incr line_length } } elseif {$line_number == 0} { append out " " } append out $item incr line_length [string length $item_notags] } append out "" return $out } # apisano 2017-06-08: this should someday replace proc # util_text_to_url, but it is unclear to me whether we want two # different semantics to sanitize URLs and filesystem names or # not. For the time being I have replaced util_text_to_url in every # place where this was used to sanitize filenames. ad_proc ad_sanitize_filename { -no_resolve:boolean {-existing_names ""} -collapse_spaces:boolean {-replace_with "-"} -tolower:boolean str } { Sanitize the provided filename for modern Windows, OS X, and Unix file systems (NTFS, ext, etc.). FAT 8.3 filenames are not supported. The generated strings should be safe against https://github.com/minimaxir/big-list-of-naughty-strings @author Gustaf Neumann } { # # Trim trailing periods and spaces (for Windows) # set str [string trim $str { .}] # # Remove Control characters (0x00–0x1f and 0x80–0x9f) # and reserved characters (/, ?, <, >, \, :, *, | and ") regsub -all {[\u0000-\u001f|/|?|<|>|\\:*|\"]+} $str "" str # allow a custom replacement char, that must be safe. regsub -all {[\u0000-\u001f|/|?|<|>|\\:*|\"|\.]+} $replace_with "" replace_with if {$replace_with eq ""} {error "-replace_with must be a safe filesystem character"} # dots other than in file extension are dangerous. Put inside two # '#' character will be seen as message keys and file-storage is # currently set to interpret them. set str_ext [file extension $str] set str_noext [string range $str 0 end-[string length $str_ext]] regsub -all {\.} $str_noext $replace_with str_noext set str ${str_noext}${str_ext} # # Remove Unix reserved filenames (. and ..) # reserved names in windows set l [string length $str] if {($l < 3 && $str in {"." ".."}) || ($l == 3 && $str in {CON PRN AUX NUL}) || ($l == 4 && $str in { COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9 }) } { set str "" } elseif {$l > 255} { # # Truncate the name to 255 characters # set str [string range $str 0 254] } # # The transformations above are necessary. The following # transformation are optional. # if {$collapse_spaces_p} { # # replace all consecutive spaces by a single char # regsub -all {[ ]+} $str $replace_with str } if {$tolower_p} { # # replace all consecutive spaces by a single "-" # set str [string tolower $str] } # check if the resulting name is already present if {$str in $existing_names} { if { $no_resolve_p } { # name is already present in the existing_names list and we # are asked to not automatically resolve the collision error "The name $str is already present" } else { # name is already present in the existing_names list - # compute an unoccupied replacement using a pattern like # this: if foo is taken, try foo-2, then foo-3 etc. # Holes will not be re-occupied. E.g. if there's foo-2 and # foo-4, a foo-5 will be created instead of foo-3. This # way confusion through replacement of deleted content # with new stuff is avoided. set number 2 foreach name $existing_names { if { [regexp "${str}${replace_with}(\\d+)\$" $name match n] } { # matches the foo-123 pattern if { $n >= $number } { set number [expr {$n + 1}] } } } set str "$str$replace_with$number" } } return $str } ad_proc -public util_text_to_url { {-existing_urls {}} {-no_resolve:boolean} {-replacement "-"} {-text ""} {_text ""} } { Modify a string so that it is suited as a well formatted URL path element. Also, if given a list of existing URLs it can catch duplicate or optionally create an unambiguous url by appending a dash and a digit.
Examples:
util_text_to_url -text "Foo Bar"
returns foo-bar
util_text_to_url -existing_urls {foo-bar some-other-item} -text "Foo Bar"
returns foo-bar-2
@param text the text to modify, e.g. "Foo Bar"
@param _text the text to modify, e.g. "Foo Bar" (Deprecated, use -text instead. Fails when the value starts with a dash.)
@param existing_urls a list of URLs that already exist on the same level and would cause a conflict
@param no_resolve Specify this flag if you do not want util_text_to_url to automatically generate
"foo-bar-2" if "foo-bar" is already in existing_urls, and would rather have an error thrown.
@param replacement the character that is used to replace illegal characters
@author Tilmann Singer
} {
if { $text eq "" } {
set text $_text
}
set original_text $text
set text [string trim [string tolower $original_text]]
# Save some german and french characters from removal by replacing
# them with their ascii counterparts.
set text [string map { \xe4 ae \xf6 oe \xfc ue \xdf ss \xf8 o \xe0 a \xe1 a \xe8 e \xe9 e } $text]
# here's the Danish ones (hm. the o-slash conflicts with the definition above, which just says 'o')
set text [string map { \xe6 ae \xf8 oe \xe5 aa \xC6 Ae \xd8 Oe \xc5 Aa } $text]
# substitute all non-word characters
regsub -all {([^a-z0-9])+} $text $replacement text
set text [string trim $text $replacement]
# throw an error when the resulting string is empty
if { $text eq "" } {
error "Cannot compute a URL of this string: \"$original_text\" because after removing all illegal characters it's an empty string."
}
# check if the resulting url is already present
if {$text in $existing_urls} {
if { $no_resolve_p } {
# URL is already present in the existing_urls list and we
# are asked to not automatically resolve the collision
error "The url $text is already present"
} else {
# URL is already present in the existing_urls list -
# compute an unoccupied replacement using a pattern like
# this: if foo is taken, try foo-2, then foo-3 etc.
# Holes will not be re-occupied. E.g. if there's foo-2 and
# foo-4, a foo-5 will be created instead of foo-3. This
# way confusion through replacement of deleted content
# with new stuff is avoided.
set number 2
foreach url $existing_urls {
if { [regexp "${text}${replacement}(\\d+)\$" $url match n] } {
# matches the foo-123 pattern
if { $n >= $number } { set number [expr {$n + 1}] }
}
}
set text "$text$replacement$number"
}
}
return $text
}
ad_proc util_email_valid_p { query_email } {
Returns 1 if an email address has more or less the correct form.
The regexp was taken from Jeff Friedls book "Mastering Regular Expressions".
@author Philip Greenspun (philg@mit.edu)
@author Jeff Friedl (jfriedl@oreilly.com)
@author Lars Pind (lars@arsdigita.com)
} {
# This regexp was very kindly contributed by Jeff Friedl, author of
# _Mastering Regular Expressions_ (O'Reilly 1997).
return [regexp "^\[^@<>\"\t ]+@\[^@<>\".\t ]+(\\.\[^@<>\".\n ]+)+$" $query_email]
}
ad_proc -public util_email_unique_p { email } {
Returns 1 if the email passed in does not yet exist in the system.
@author yon (yon@openforce.net)
} {
return [db_string email_unique_p {}]
}
ad_proc -public util_url_valid_p { query_url } {
Returns 1 if a URL is a web URL (HTTP, HTTPS or FTP).
@author Philip Greenspun (philg@mit.edu)
} {
return [regexp -nocase {^(http|https|ftp)://[^ ].+} [string trim $query_url]]
}
ad_proc -public value_if_exists { var_name } {
If the specified variable exists in the calling environment,
returns the value of that variable. Otherwise, returns the
empty_string.
} {
upvar $var_name $var_name
if { [info exists $var_name] } {
return [set $var_name]
}
}
ad_proc -public min { args } {
Returns the minimum of a list of numbers. Example: min 2 3 1.5
returns 1.5.
@author Ken Mayer (kmayer@bitwrangler.com)
@creation-date 26 September 2002
} {
set min [lindex $args 0]
foreach arg $args {
if { $arg < $min } {
set min $arg
}
}
return $min
}
ad_proc -public max { args } {
Returns the maximum of a list of numbers. Example: max 2 3 1.5
returns 3.
@author Lars Pind (lars@pinds.com)
@creation-date 31 August 2000
} {
set max [lindex $args 0]
foreach arg $args {
if { $arg > $max } {
set max $arg
}
}
return $max
}
ad_proc -public util_ns_set_to_list {
{-set:required}
} {
Convert an ns_set into a list suitable for passing in to the "array set" command (key value key value ...).
@param set The ns_set to convert
@return An array of equivalent keys and values as the ns_set specified.
} {
set result [list]
for {set i 0} {$i < [ns_set size $set]} {incr i} {
lappend result [ns_set key $set $i]
lappend result [ns_set value $set $i]
}
return $result
}
ad_proc -public util_list_to_ns_set { aList } {
Convert a list in the form "key value key value ..." into a ns_set.
@param aList The list to convert
@return The id of a (non-persistent) ns_set
} {
set setid [ns_set create]
foreach {k v} $aList {
ns_set put $setid $k $v
}
return $setid
}
ad_proc -public util_sets_equal_p { list1 list2 } {
Tests whether each unique string in list1 occurs as many
times in list1 as in list2 and vice versa (regardless of order).
@return 1 if the lists have identical sets and 0 otherwise
@author Peter Marklund
} {
if { [llength $list1] != [llength $list2] } {
return 0
}
set sorted_list1 [lsort $list1]
set sorted_list2 [lsort $list2]
for { set index1 0 } { $index1 < [llength $sorted_list1] } { incr index1 } {
if { [lindex $sorted_list1 $index1] ne [lindex $sorted_list2 $index1] } {
return 0
}
}
return 1
}
ad_proc -public util_list_of_ns_sets_to_list_of_lists {
{-list_of_ns_sets:required}
} {
Transform a list of ns_sets (most likely produced by db_list_of_ns_sets)
into a list of lists that match the array set format in the sublists
(key value key value ...)
@param list_of_ns_sets A list of ns_set ids
@author Ola Hansson (ola@polyxena.net)
@creation-date September 27, 2002
} {
set result [list]
foreach ns_set $list_of_ns_sets {
lappend result [util_ns_set_to_list -set $ns_set]
}
return $result
}
ad_proc -public xml_get_child_node_content_by_path {
node
path_list
} {
Return the first non-empty contents of a child node down a given path from the current node.
Example:
set tree [xml_parse -persist { <enterprise> <properties> <datasource>Dunelm Services Limited</datasource> <target>Telecommunications LMS</target> <type>DATABASE UPDATE</type> <datetime>2001-08-08</datetime> </properties> <person recstatus = "1"> <comments>Add a new Person record.</comments> <sourcedid> <source>Dunelm Services Limited</source> <id>CK1</id> </sourcedid> <name> <fn>Clark Kent</fn> <sort>Kent, C</sort> <nickname>Superman</nickname> </name> <demographics> <gender>2</gender> </demographics> <adr> <extadd>The Daily Planet</extadd> <locality>Metropolis</locality> <country>USA</country> </adr> </person> </enterprise> }] set root_node [xml_doc_get_first_node $tree] aa_equals "person -> name -> nickname is Superman" \ [xml_get_child_node_content_by_path $root_node { { person name nickname } }] "Superman" aa_equals "Same, but after trying a couple of non-existent paths or empty notes" \ [xml_get_child_node_content_by_path $root_node { { does not exist } { properties } { person name nickname } { person sourcedid id } }] "Superman" aa_equals "properties -> datetime" \ [xml_get_child_node_content_by_path $root_node { { person comments foo } { person name first_names } { properties datetime } }] "2001-08-08"@param node The node to start from @param path_list List of list of nodes to try, e.g. { { user_id } { sourcedid id } }, or { { name given } { name fn } }. @author Lars Pind (lars@collaboraid.biz) } { set result {} foreach path $path_list { set current_node $node foreach element_name $path { set current_node [xml_node_get_first_child_by_name $current_node $element_name] if { $current_node eq "" } { # Try the next path break } } if { $current_node ne "" } { set result [xml_node_get_content $current_node] if { $result ne "" } { # Found the value, we're done break } } } return $result } ad_proc -public xml_get_child_node_attribute_by_path { node path_list attribute_name } { Return the attribute of a child node down a give path from the current node. Example:
set tree [xml_parse -persist " <enterprise> <properties> <datasource>University of Durham: SIS</datasource> <target>University of Durham: LMS</target> <type>CREATE</type> <datetime>2001-08-08</datetime> </properties> <group recstatus = "1"> <sourcedid> <source>University of Durham</source> <id>CS1</id> </sourcedid> <grouptype> <scheme>University of Durham</scheme> <typevalue level = "2"/> </grouptype> ..... </group> </enterprise> "] set root_node [xml_doc_get_first_node $tree] set group_node [xml_node_get_children_by_name $root_node "group"] set typevalue [xml_get_child_node_attribute_by_path $group_node {grouptype typevalue} "level"]@param node The node to start from @param path_list List of the node to try, e.g. { grouptype typevalue }. @param attribute_name Attribute name at the very end of the very botton of the tree route at path_list. @author Rocael Hernandez (roc@viaro.net) } { set attribute {} set current_node $node foreach element_name $path_list { set current_node [xml_node_get_first_child_by_name $current_node $element_name] if { $current_node eq "" } { # Try the next path break } } if { $current_node ne "" } { set attribute [xml_node_get_attribute $current_node $attribute_name ""] } return $attribute } ad_proc -public ad_generate_random_string { {length 8} } { Generates a random string made of numbers and letters } { return [string range [sec_random_token] 0 $length] } ad_proc -public with_finally { -code:required -finally:required } { Execute CODE, then execute cleanup code FINALLY. If CODE completes normally, its value is returned after executing FINALLY. If CODE exits non-locally (as with error or return), FINALLY is executed anyway. @param code Code to be executed that could throw and error @param finally Cleanup code to be executed even if an error occurs } { # Execute CODE. set return_code [catch {uplevel $code} string] if {[info exists ::errorInfo]} { set s_errorInfo $::errorInfo } else { set s_errorInfo "" } if {[info exists ::errorCode]} { set s_errorCode $::errorCode } else { set s_errorCode "" } # As promised, always execute FINALLY. If FINALLY throws an # error, Tcl will propagate it the usual way. If FINALLY contains # stuff like break or continue, the result is undefined. uplevel $finally switch -- $return_code { 0 { # CODE executed without a non-local exit -- return what it # evaluated to. return $string } 1 { # Error if {[lindex $s_errorCode 0 0] eq "CHILDSTATUS"} { # # GN: In case the errorCode starts with CHILDSTATUS it # means that an error was raised from an "exec". In # that case the raw error just tells that the "child # process exited abnormally", without given any # details. Therefore we add the exit code to the # messages. # set extra "child process (pid [lindex $s_errorCode 0 1]) exited with exit-code [lindex $s_errorCode 0 end]" append string " ($extra)" set s_errorInfo $extra\n$s_errorInfo } return -code error -errorinfo $s_errorInfo -errorcode $s_errorCode $string } 2 { # Return from the caller. return -code return $string } 3 { # break return -code break } 4 { # continue return -code continue } default { return -code $return_code $string } } } ad_proc util_background_exec { {-pass_vars ""} {-name:required} code_chunk } { Executes a chunk of code in the background. The code is run exclusively, meaning that no two threads with the same name can run at the same time. @param name The name of the thread. No two chunks with the same name can run at the same time. @param pass_vars Names of variables which you want passed to the code chunk @param code_chunk The chunk you want executed } { ns_log Debug "util_background_exec: Starting, waiting for mutex" # ns_mutex lock [nsv_get util_background_exec_mutex .] ns_log Debug "util_background_exec: Got mutex" set running_p [nsv_exists util_background_exec $name] if { !$running_p } { nsv_set util_background_exec [list $name] 1 } # ns_mutex unlock [nsv_get util_background_exec_mutex .] ns_log Debug "util_background_exec: Released mutex" if { $running_p } { ns_log Notice "util_background_exec: $name is already running, exiting" return } set code {} foreach var $pass_vars { upvar 1 $var the_var if { [array exists the_var] } { append code "array set [list $var] [list [array get the_var]]\n" } else { append code "set [list $var] [list $the_var]\n" } } append code " set errno \[catch { $code_chunk } errmsg\] set errinfo {} set errcode {} if { \$errno == 1 } { set errinfo \$::errorInfo set errcode \$::errorCode } if { \$errno == 1 } { \# This is an error ns_log Error \"util_background_exec: Error in thread named '$name': \$::errorInfo\" } \# errno = 0 (TCL_OK) or 2 (TCL_RETURN) is considered normal, i.e. first elm is true set success_p \[expr { \$errno == 0 || \$errno == 2 }\] set result \[list \$success_p \$errmsg \$errno \$errinfo \$errcode] ns_log debug \"util_background_exec: Thread named '$name' returned \$result\" nsv_unset util_background_exec [list $name] nsv_set util_background_exec_result [list $name] \$result " ns_log Debug "util_background_exec: Scheduling code\n$code" ns_schedule_proc -thread -once 1 $code } ad_proc util_background_running_p { {-name:required} } { } { set running_p [nsv_exists util_background_exec $name] return $running_p } ad_proc util_background_get_result { {-name:required} } { Gets the result of a completed background thread execution. } { return [nsv_get util_background_exec_result $name] } ad_proc util_background_reset { {-name:required} } { Gets the result of a completed background thread execution. } { nsv_unset util_background_exec $name } ##### # # This is some old security crud from before we had ad_page_contract # ##### # # All the ad_var_type_check* procs get called from # check_for_form_variable_naughtiness. Read the documentation # for ad_set_typed_form_variable_filter for more details. ad_proc ad_var_type_check_integer_p {value} { @return 1 if $value is an integer, 0 otherwise. } { if { [regexp {[^0-9]} $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_safefilename_p {value} { @return 0 if the file contains ".." } { if { [string match "*..*" $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_dirname_p {value} { @return 0 if $value contains a / or \, 1 otherwise. } { if { [regexp {[/\\]} $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_number_p {value} { @return 1 if $value is a valid number } { if { [catch {expr {1.0 * $value}}] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_word_p {value} { @return 1 if $value contains only letters, numbers, dashes, and underscores, otherwise returns 0. } { if { [regexp {[^-A-Za-z0-9_]} $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_nocheck_p {{value ""}} { @return 1 regardless of the value. This is useful if you want to set a filter over the entire site, then create a few exceptions. For example: ad_set_typed_form_variable_filter /my-dangerous-page.tcl {user_id nocheck} ad_set_typed_form_variable_filter /*.tcl user_id } { return 1 } ad_proc ad_var_type_check_noquote_p {value} { @return 1 if $value contains any single-quotes } { if { [string match "*'*" $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_integerlist_p {value} { @return 1 if list contains only numbers, spaces, and commas. Example '5, 3, 1'. Note: it doesn't allow negative numbers, because that could let people sneak in numbers that get treated like math expressions like '1, 5-2' } { if { [regexp {[^ 0-9,]} $value] } { return 0 } else { return 1 } } ad_proc ad_var_type_check_fail_p {value} { A check that always returns 0. Useful if you want to disable all access to a page. } { return 0 } ad_proc ad_var_type_check_third_urlv_integer_p {{args ""}} { @return 1 if the third path element in the URL is integer. } { set third_url_element [lindex [ad_conn urlv] 3] if { [regexp {[^0-9]} $third_url_element] } { return 0 } else { return 1 } } #################### # # Procs in the util namespace # #################### ad_proc util::name_to_path { -name:required } { Transforms a pretty name to a reasonable path name. } { regsub -all -nocase { } [string trim [string tolower $name]] {-} name regsub -all {[^[:alnum:]\-]} $name {} name return $name } ad_proc -public util::backup_file { {-file_path:required} {-backup_suffix ".bak"} } { Backs up (move) the file or directory with given path to a file/directory with a backup suffix. Will avoid overwriting old backup files by adding a number to the filename to make it unique. For example, suppose you are backing up /web/my-server/packages/my-package/file.txt and the file has already been backed up to /web/my-server/packages/my-package/file.txt.bak. Invoking this proc will then generate the backup file /web/my-server/packages/my-package/file.txt.bak.2 @param backup_suffix The suffix to add to the backup file. @author Peter Marklund } { # Keep generating backup paths until we find one that doesn't already exist set backup_counter 1 while {1} { if { $backup_counter == 1 } { set backup_path "${file_path}${backup_suffix}" } else { set backup_path "${file_path}${backup_suffix}.${backup_counter}" } if { ![file exists $backup_path] } { # We found a non-existing backup path break } incr backup_counter } #exec "mv" "$file_path" "$backup_path" file rename -- $file_path $backup_path } ad_proc -public util::subst_safe { string } { Make string safe for subst'ing. } { regsub -all {\$} $string {\$} string regsub -all {\[} $string {\[} string regsub -all {\]} $string {\]} string return $string } ad_proc -public util::array_list_spec_pretty { list {indent 0} } { Pretty-format an array-list spec with proper indentation. } { set output {} foreach { elm val } $list { if { [llength $val] > 1 && [llength $val] % 2 == 0 } { append output [string repeat " " $indent] "$elm \{" \n append output [util::array_list_spec_pretty $val [expr {$indent + 4}]] append output [string repeat " " $indent] \} \n } else { append output [string repeat " " $indent] [list $elm] " " [list $val] \n } } return $output } ad_proc -public util::interval_pretty { {-seconds 0} } { Takes a number of seconds and returns a pretty interval of the form "3h 49m 13s" } { set result {} if { $seconds > 0 } { set hrs [expr {$seconds / (60*60)}] set mins [expr {($seconds / 60) % 60}] set secs [expr {$seconds % 60}] if { $hrs > 0 } { append result "${hrs}h " } if { $hrs > 0 || $mins > 0 } { append result "${mins}m " } append result "${secs}s" } return $result } ad_proc -public util::randomize_list { list } { Returns a random permutation of the list. } { set len [llength $list] set result [list] while { [llength $list] > 0 } { set index [randomRange [expr {[llength $list] - 1}]] lappend result [lindex $list $index] set list [lreplace $list $index $index] } return $result } ad_proc -public util::random_list_element { list } { Returns a random element from the list. } { set len [llength $list] set idx [expr {int(rand() * $len)}] return [lindex $list $idx] } ad_proc -public util::age_pretty { -timestamp_ansi:required -sysdate_ansi:required {-hours_limit 12} {-days_limit 3} {-mode_2_fmt "%X, %A"} {-mode_3_fmt "%X, %d %b %Y"} {-locale ""} } { Formats past time intervals in one of three different modes depending on age. The first mode is "1 hour 3 minutes" and is NOT currently internationalized. The second mode is e.g. "14:10, Thursday" and is internationalized. The third mode is "14:10, 01 Mar 2001" and is internationalized. Both the locale and the exact format string for modes 2 and 3 can be overridden by parameters. (Once mode 1 is i18nd, the following sentence will be true:'In mode 1, only the locale can be overridden.' Until then, move along. These aren't the timestamps you're looking for.) @param timestamp_ansi The older timestamp in full ANSI: YYYY-MM-DD HH24:MI:SS @param sysdate_ansi The newer timestamp. @param hours_limit The upper limit, in hours, for mode 1. @param days_limit The upper limit, in days, for mode 2. @param mode_2_fmt A formatting string, as per lc_time_fmt, for mode 2 @param mode_3_fmt A formatting string, as per lc_time_fmt, for mode 3 @param locale If present, overrides the default locale @return Interval between timestamp and sysdate, as localized text string. } { set age_seconds [expr {[clock scan $sysdate_ansi] - [clock scan $timestamp_ansi]}] if { $age_seconds < 30 } { # Handle with normal processing below -- otherwise this would require another string to localize set age_seconds 60 } if { $age_seconds < $hours_limit * 60 * 60 } { set hours [expr {abs($age_seconds / 3600)}] set minutes [expr {round(($age_seconds% 3600)/60.0)}] if {$hours < 24} { switch -- $hours { 0 { set result "" } 1 { set result "One hour " } default { set result "$hours hours "} } switch -- $minutes { 0 {} 1 { append result "$minutes minute " } default { append result "$minutes minutes " } } } else { set days [expr {abs($hours / 24)}] switch -- $days { 1 { set result "One day " } default { set result "$days days "} } } append result "ago" } elseif { $age_seconds < $days_limit * 60 * 60 * 24 } { set result [lc_time_fmt $timestamp_ansi $mode_2_fmt $locale] } else { set result [lc_time_fmt $timestamp_ansi $mode_3_fmt $locale] } } ad_proc -public util::word_diff { {-old:required} {-new:required} {-split_by {}} {-filter_proc {ns_quotehtml}} {-start_old {