Index: openacs-4/packages/acs-templating/tcl/element-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-templating/tcl/element-procs.tcl,v diff -u -r1.37 -r1.38 --- openacs-4/packages/acs-templating/tcl/element-procs.tcl 18 May 2018 07:56:07 -0000 1.37 +++ openacs-4/packages/acs-templating/tcl/element-procs.tcl 3 Sep 2024 15:37:35 -0000 1.38 @@ -17,22 +17,25 @@ namespace eval template {} namespace eval template::element {} -ad_proc -public element { command form_id element_id args } { +ad_proc -deprecated element { command form_id element_id args } { element is really template::element although when in the "template" namespace you may omit the template:: qualifier. See the template::form API for creating the form element container. + DEPRECATED: please use the properly namespaced api + @see template::element @see template::form -} - + @see template::element +} - ad_proc -public template::element { command form_id element_id args } { - Manage elements of form objects. + Manage elements of form objects.

see the individual commands for further information. - @param command one of create, error_p, exists, get_property, get_value, - get_values, querygetall, set_error, set_properties, set_value - @param form_id string identifying the form + @param command one of create, error_p, exists, get_property, get_value, + get_values, querygetall, set_error, set_properties, set_value + @param form_id string identifying the form @param element_id string identifying the element @see template::element::create @@ -44,163 +47,327 @@ @see template::element::querygetall @see template::element::set_error @see template::element::set_properties - @see template::element::set_value - + @see template::element::set_value + @see template::form } { - template::element::$command $form_id $element_id {*}$args + template::element::$command $form_id $element_id {*}$args } +ad_proc -private template::element::get_opts { + -widget + -datatype + -label + -html + -maxlength + -options + -fieldset + -legend + -legendtext + -value + -values + -validate + -sign:boolean + -help_text + -help:boolean + -optional:boolean + -mode + -nospell:boolean + -noquote:boolean + -before_html + -after_html + -display_value + -multiple:boolean + -format + -section + -htmlarea_p + args +} { + template::element::create syntax requires first two non-positional + arguments (form and element name), then a set of named-argument + flags to transform into options. This is not the native Tcl + syntax, where named-arguments come before unnamed ones. To use + native Tcl argument parsing for remaining flags, we create this + internal utility. + + @see template::element::create + + @return a dict of options +} { + set opts [list] + # + ## These are the documented options we expect in widgets. We know + ## exactly which are key value pairs and which ones are one-valued + ## booleans + # + set flags { + widget + datatype + label + html + maxlength + options + fieldset + legend + legendtext + value + values + validate + help_text + mode + before_html + after_html + display_value + format + section + htmlarea_p + } + set booleans { + sign + help + optional + nospell + noquote + multiple + } + foreach flag $flags { + if {[info exists $flag]} { + dict set opts $flag [set $flag] + } + } + foreach boolean $booleans { + if {[set ${boolean}_p]} { + dict set opts $boolean 1 + } + } + # + ## If additional args are found, we might deal with custom options + ## from user-defined widgets. We treat all of those as name value + ## pairs. + ## TODO: one could consider having custom argument parsers for + ## those widgets. + # + while {[llength $args] > 0} { + set arg [string range [lindex $args 0] 1 end] + if {![dict exists $opts $arg]} { + # Collect this name value pair as a custom option + set name $arg + set value [lindex $args 1] + dict set opts $name $value + #ad_log notice "Collecting custom option for template::element name=$name, value=$value" + } + if {$arg in $booleans} { + # When this is one of the known booleans, just skip one + # argument. + set args [lrange $args 1 end] + } else { + # In all other cases, skip 2 arguments (treat options as + # name value pairs). + set args [lrange $args 2 end] + } + } + # After parsing of custom arguments, the list should be + # empty. That we still have stuff in there means something is not + # ok with the element specs. + if {[llength $args] > 0} { + ad_log warning "Ignoring undocumented args for template::element $args" + } + return $opts +} + ad_proc -public template::element::create { form_id element_id args } { Append an element to a form object. If a submission is in progress, values for the element are prepared and validated. - @param form_id The identifier of the form to which the element is to - be added. The form must have been previously created - with a form create statement. + @param form_id The identifier of the form to which the element is + to be added. The form must have been + previously created with a form + create statement. - @param element_id A keyword identifier for the element that is unique - in the context of the form. + @param element_id A keyword identifier for the element that is + unique in the context of the form. @option widget The name of an input widget for the element. Valid - widgets must have a rendering procedure defined in - the template::widget namespace. + widgets must have a rendering procedure + defined in the template::widget + namespace. - @option datatype The name of a datatype for the element values. Valid - datatypes must have a validation procedure defined in - the template::data::validate namespace. + @option datatype The name of a datatype for the element values. + Valid datatypes must have a validation + procedure defined in the + template::data::validate namespace. @option label The label for the form element. - + @option html A list of name-value attribute pairs to include in - the HTML tag for widget. Typically used for additional - formatting options, such as cols or - rows, or for JavaScript handlers. + the HTML tag for widget. Typically used for + additional formatting options, such as + cols or rows, or for + JavaScript handlers. - @option maxlength The maximum allowable length in bytes. Will be checked using - 'string bytelength'. Will also cause 'input' widgets (text, integer, etc.) + @option maxlength The maximum allowable length in bytes. Will be + checked using 'string bytelength'. Will also + cause 'input' widgets (text, integer, etc.) to get a maxlength="..." attribute. - @option options A list of options for select lists and button groups - (check boxes or radio buttons). The list contains - two-element lists in the form - { {label value} {label value} {label value} ...} + @option options A list of options for select lists and button + groups (check boxes or radio buttons). The + list contains two-element lists in the form + { {label value} {label value} {label value} + ...} - @option fieldset A list of name-value attribute pairs to include in - the HTML tag for checkboxes and radio FIELDSET. + @option fieldset A list of name-value attribute pairs to include + in the HTML tag for checkboxes and radio + FIELDSET. - @option legend A list of name-value attribute pairs to include in - the HTML tag for checkboxes and radio LEGEND. + @option legend A list of name-value attribute pairs to include in + the HTML tag for checkboxes and radio + LEGEND. - @option legendtext A text for the LEGEND tag to include in - the checkboxes and radio FIELDSET block + @option legendtext A text for the LEGEND tag to include in the + checkboxes and radio FIELDSET block @option value The default value of the element - @option values The default values of the element, where multiple values - are allowed (checkbox groups and multiselect widgets) + @option values The default values of the element, where multiple + values are allowed (checkbox groups and + multiselect widgets) @option validate A list of custom validation blocks in the form - { name { expression } { message } \ - name { expression } { message } ...} - where name is a unique identifier for the validation - step, expression is a block to Tcl code that evaluates to - 1 or 0, and message is to be displayed to the user when - the validation step fails, that is, if the expression - evaluates to 0. Use the special variable $value - to refer to the value entered by the user in that field. + {name { script } { message } \ + name { script } { message } ...} + where name is a unique identifier for the + validation step, expression is a block to + Tcl code (script) that should set the result + to 1 or 0, and message is to be displayed to + the user when the validation step fails, + that is, if the expression evaluates to + 0. Use the special variable $value + to refer to the value entered by the user in + that field. Note that e.g. in ad_form, all + blocks are substituted, therefore, the + script might require escaping. - @option sign specify for a hidden widget that its value should be + @option sign Specify for a hidden widget that its value should be signed - @option help_text Text displayed with the element + @option help_text Text displayed with the element @option help Display helpful hints (date widget only?) - @option optional A flag indicating that no value is required for this - element. If a default value is specified, the default - is used instead. + @option optional A flag indicating that no value is required for + this element. If a default value is + specified, the default is used instead. - @option mode Valid values are 'display', 'edit', and the empty string. - If set to 'display', the element will render as static HTML - which doesn't allow editing of the value, instead of the - HTML form element (e.g. <input>) which would otherwise - get used. If set to 'edit', the element is as normal, allowing - the user to edit the contents. If set to the empty string or - not specified at all, the form's 'mode' setting is used instead. + @option mode Valid values are 'display', 'edit', and the empty + string. If set to 'display', the element + will render as static HTML which doesn't + allow editing of the value, instead of the + HTML form element (e.g. <input>) which + would otherwise get used. If set to 'edit', + the element is as normal, allowing the user + to edit the contents. If set to the empty + string or not specified at all, the form's + 'mode' setting is used instead. - @option nospell A flag indicating that no spell-checking should be performed on - this element. This overrides the 'SpellcheckFormWidgets' parameter. + @option nospell A flag indicating that no spell-checking should be + performed on this element. This overrides + the 'SpellcheckFormWidgets' parameter. + @option noquote A flag indicating that no value should not be + quoted in a form. In addition, the + nonquoted inform field is not transmitted as + a hidden field (which can be attacked via + noquote). Currently only supported by the + "inform" widget type. + @option before_html A chunk of HTML displayed immediately before the rendered element. @option after_html A chunk of HTML displayed immediately after the rendered element. - @option display_value Alternative value used when the element is in display mode. - If specified, this value is used when the mode is set to 'display', - instead of asking the element widget to render itself in display mode. + @option display_value Alternative value used when the element is + in display mode. If specified, this value + is used when the mode is set to 'display', + instead of asking the element widget to + render itself in display mode. + @option multiple A flag indicating that more than one value is + expected from the input element + + @option format Many form elements allow one to specify a format, + e.g. a way the element should be displayed + or interpret its value. Refer to the + specific widgets for the actual behavior. + + @option section Specify to which form section this element belongs + + @option htmlarea_p Only relevant for textarea kind of elements, + tells if the element is supposed to be + rendered as a richtext editor or not. + @see template::widget @see template::data::validate @see template::form::create @see template::form::section } { - set level [template::adp_level] + set level [template::adp_level] - # add the element to the element list - upvar #$level $form_id:elements elements $form_id:properties form_properties - if { ! [info exists elements] } { - error "Form $form_id does not exist" - } + # add the element to the element list + upvar #$level $form_id:elements elements $form_id:properties form_properties + if { ! [info exists elements] } { + error "Form $form_id does not exist" + } - lappend elements $form_id:$element_id - lappend form_properties(element_names) $element_id + lappend elements $form_id:$element_id + lappend form_properties(element_names) $element_id - # add the reference to the elements lookup array for the form - upvar #$level $form_id:$element_id opts + # add the reference to the elements lookup array for the form + upvar #$level $form_id:$element_id opts - if {[info exists opts]} { - error "Element '$element_id' already exists in form '$form_id'." - } + if {[info exists opts]} { + error "Element '$element_id' already exists in form '$form_id'." + } - set opts(id) $element_id - set opts(form_id) $form_id + set opts(id) $element_id + set opts(form_id) $form_id - # ensure minimal defaults for element parameters - variable defaults - array set opts $defaults + # ensure minimal defaults for element parameters + variable defaults + array set opts $defaults - # By default, the form/edit mode is set to the empty string - # Can be set to something else if you want - set opts(mode) {} + # By default, the form/edit mode is set to the empty string + # Can be set to something else if you want + set opts(mode) {} - # set the form section - set opts(section) $form_properties(section) - if { $opts(section) ne "" } { - array set opts {sec_fieldset "" sec_legend "" sec_legendtext ""} - if {[info exists form_properties(sec_fieldset)]} {set opts(sec_fieldset) $form_properties(sec_fieldset)} - if {[info exists form_properties(sec_legend)]} {set opts(sec_legend) $form_properties(sec_legend)} - if {[info exists form_properties(sec_legendtext)]} {set opts(sec_legendtext) $form_properties(sec_legendtext)} - } + # set the form section + set opts(section) $form_properties(section) + if { $opts(section) ne "" } { + array set opts {sec_fieldset "" sec_legend "" sec_legendtext ""} + if {[info exists form_properties(sec_fieldset)]} {set opts(sec_fieldset) $form_properties(sec_fieldset)} + if {[info exists form_properties(sec_legend)]} {set opts(sec_legend) $form_properties(sec_legend)} + if {[info exists form_properties(sec_legendtext)]} {set opts(sec_legendtext) $form_properties(sec_legendtext)} + } - template::util::get_opts $args + if {[llength $args] > 0} { + array set opts [::template::element::get_opts {*}$args] + } - # set a name if none specified - if { ! [info exists opts(name)] } { set opts(name) $opts(id) } + # set a name if none specified + if { ! [info exists opts(name)] } { set opts(name) $opts(id) } - # set a label if none specified - if { ! [info exists opts(label)] } { set opts(label) $element_id } + # set a label if none specified + if { ! [info exists opts(label)] } { set opts(label) $element_id } - # If the widget is a submit widget, remember it - # All submit widgets are optional - if { $opts(widget) eq "submit" || $opts(widget) eq "button" } { - set form_properties(has_submit) 1 - set opts(optional) 1 - if { ! [info exists opts(value)] } { set opts(value) $opts(label) } - if { ! [info exists opts(label)] } { set opts(label) $opts(value) } - } + # If the widget is a submit widget, remember it + # All submit widgets are optional + if { $opts(widget) eq "submit" || $opts(widget) eq "button" } { + set form_properties(has_submit) 1 + set opts(optional) 1 + if { ! [info exists opts(value)] } { set opts(value) $opts(label) } + if { ! [info exists opts(label)] } { set opts(label) $opts(value) } + } - # If the widget is a checkbox or radio widget, set attributes + # If the widget is a checkbox or radio widget, set attributes if { $opts(widget) eq "radio" || $opts(widget) eq "checkbox" } { # If there's no legend text, no point to generate the fieldset @@ -246,40 +413,52 @@ } } - # Remember that the element has not been rendered yet - set opts(is_rendered) f + # Remember that the element has not been rendered yet + set opts(is_rendered) f - copy_value_to_values_if_defined + copy_value_to_values_if_defined - # check for submission - if { [template::form is_submission $form_id] || [info exists opts(param)] } { - validate $form_id $element_id - } elseif { [ns_queryget "__edit"] ne "" } { - # If the magic __edit button was hit, try to get values from the form still - # but don't do any validation - set opts(values) [querygetall opts] + # check for submission + if { [template::form is_submission $form_id] || [info exists opts(param)] } { - # be careful not to clobber a default value if one has been specified - if { [llength $opts(values)] || ! [info exists opts(value)] } { - set opts(value) [lindex $opts(values) 0] + if {[info exists opts(param)]} { + ad_log warning "Outdated and deprecated form options detected," \ + "The usage of opts(param) will be removed in versions past 5.10.1" + } + + validate $form_id $element_id + } elseif { [ns_queryget "__edit"] ne "" } { + # If the magic __edit button was hit, try to get values from the form still + # but don't do any validation + set opts(values) [querygetall opts] + + ad_log warning "This if-branch is insecure since it bypasses validation." \ + "the branch is deactivated rigjt now, and there is no know usage" \ + "of the __edit flag. If you still need it, uncomment the following line" \ + "and contact webmaster@openacs.org" + error "Outdated and vulnerable code detected, contact webmaster@openacs.org" + + # be careful not to clobber a default value if one has been specified + if { [llength $opts(values)] || ! [info exists opts(value)] } { + set opts(value) [lindex $opts(values) 0] + } } - } - if { $opts(widget) eq "hidden" - && [info exists opts(sign)] - && $opts(sign) - } { - if {[info exists opts(value)] } { - set val $opts(value) - } else { - set val "" - } - template::element::create $opts(form_id) $opts(id):sig \ - -datatype text \ - -widget hidden \ - -section $opts(section) \ - -value [ad_sign $val] - } + if { $opts(widget) eq "hidden" + && [info exists opts(sign)] + && $opts(sign) + } { + if {[info exists opts(value)] } { + set val $opts(value) + } else { + set val "" + } + template::element::create $opts(form_id) $opts(id):sig \ + -datatype text \ + -widget hidden \ + -section $opts(section) \ + -value [ad_sign $val] + } } ad_proc -public template::element::set_properties { form_id element_id args } { @@ -292,16 +471,16 @@ @see template::element::create } { - get_reference + get_reference - # create a reference to opts as expected by get_opts - upvar 0 element opts + # create a reference to opts as expected by get_opts + upvar 0 element opts - template::util::get_opts $args + template::util::get_opts $args if { $opts(widget) eq "hidden" - && [info exists opts(sign)] - && $opts(sign) + && [info exists opts(sign)] + && $opts(sign) && [info exists opts(value)] } { if { [template::element::exists $form_id $element_id:sig] } { template::element::set_properties $form_id $element_id:sig \ @@ -315,7 +494,7 @@ } } - copy_value_to_values_if_defined + copy_value_to_values_if_defined } ad_proc -public template::element::set_value { form_id element_id value } { @@ -326,10 +505,10 @@ @param value The value to apply } { - get_reference + get_reference - set element(value) $value - set element(values) [list $value] + set element(value) $value + set element(values) [list $value] } ad_proc -public template::element::set_values { form_id element_id values } { @@ -340,9 +519,9 @@ @param values The list of values to apply } { - get_reference + get_reference - set element(values) $values + set element(values) $values } ad_proc -public template::element::get_value { form_id element_id } { @@ -355,31 +534,31 @@ @return The current value of the element. @see template::element::get_values } { - get_reference + get_reference - if { [info exists element(value)] } { - regsub {