Class ::xowiki::FormPage

::xowiki::FormPage[i] create ...

Defined in

Class Relations

  • class: ::xo::db::CrClass[i]
  • superclass: ::xowiki::PageInstance[i]
::xo::db::CrClass create ::xowiki::FormPage \
     -superclass ::xowiki::PageInstance

Methods (to be applied on the object)

  • compute_filter_clauses (scripted, public)

     xowiki::FormPage[i] compute_filter_clauses [ -unless unless ] \
        [ -where where ]

    Compute from "-unless" or "-where" specs the "tcl", "sql" and optional "hstore" query fragments.

    Switches:
    -unless (optional)
    -where (optional)
    Returns:
    dict containing "init_vars", "uc" (unless clauses) and "wc" (where clauses)

    Testcases:
    create_composite_test_item
    
    set init_vars [list]
    set uc {tcl false h "" vars "" sql ""}
    if {[info exists unless]} {
      set uc [dict merge $uc [:filter_expression $unless ||]]
      set init_vars [list {*}$init_vars {*}[dict get $uc vars]]
    }
    set wc {tcl true h "" vars "" sql ""}
    if {[info exists where]} {
      set wc [dict merge $wc [:filter_expression $where &&]]
      set init_vars [list {*}$init_vars {*}[dict get $wc vars]]
    }
    return [list init_vars $init_vars uc $uc wc $wc]
  • fetch_object (scripted, public)

     xowiki::FormPage[i] fetch_object -item_id item_id  \
        [ -revision_id revision_id ] -object object  \
        [ -initialize on|off ]

    Load a content item into the specified object. If revision_id is provided, the specified revision is returned, otherwise the live revision of the item_id. If the object does not exist, we create it.

    Switches:
    -item_id (required)
    -revision_id (optional, defaults to "0")
    -object (required)
    -initialize (optional, boolean, defaults to "true")
    Returns:
    cr item object

    Testcases:
    xowiki_test_cases, link_tests, path_resolve
    #ns_log notice "=== fetch_object $item_id"
    #
    # We handle here just loading object instances via item_id, since
    # only live_revisions are kept in xowiki_form_instance_item_index.
    # The loading via revision_id happens as before in CrClass.
    #
    if {$item_id == 0} {
      return [next]
    }
    
    if {![nsf::is object $object]} {
      # if the object does not yet exist, we have to create it
      :create $object
    }
    
    db_with_handle db {
      set sql [::xo::dc prepare -handle $db -argtypes integer {
        select * from xowiki_form_instance_item_view where item_id = :item_id
      }]
      set selection [db_exec 0or1row $db dbqd..Formpage-fetch_object $sql]
    }
    
    if {$selection eq ""} {
      error [subst {
        The form page with item_id $item_id was not found in the
        xowiki_form_instance_item_index.  Consider 'DROP TABLE
        xowiki_form_instance_item_index CASCADE;' and restart server
        (the table is rebuilt automatically, but this could take a
        while, when the number of pages is huge).
      }]
    }
    
    $object mset [ns_set array $selection]
    
    if {$initialize} {
      $object initialize_loaded_object
    }
    return $object
  • filter_expression (scripted)

     xowiki::FormPage[i] filter_expression

    Testcases:
    xowiki_test_cases, xowiki
    #ns_log notice "filter_expression '$input_expr' $logical_op"
    
    #
    # example for unless: wf_current_state = closed|accepted || x = 1
    #
    
    array set tcl_op {= eq < < > > >= >= <= <=}
    array set sql_op {= =  < < > > >= >= <= <=}
    array set op_map {
      contains,sql {$lhs_var like '%$sql_rhs%'}
      contains,tcl {{$rhs} in $lhs_var}
      matches,sql {$lhs_var like '$sql_rhs'}
      matches,tcl {[string match "$rhs" $lhs_var]}
    }
    
    set tcl_clause [list]
    set h_clause [list]
    set vars [list]
    set sql_clause [list]
    foreach clause [split [string map [list $logical_op \x00] $input_expr] \x00] {
      if {[regexp {^(.*[^<>])\s*([=<>]|<=|>=|contains|matches)\s*([^=]?.*)$} $clause _ lhs op rhs_expr]} {
        set lhs [string trim $lhs]
        set rhs_expr [string trim $rhs_expr]
        if {[string index $lhs 0] eq "_"} {
          #
          # Comparison with field names starting with "_"
          #
          set lhs_var [string range $lhs 1 end]
          set rhs [split $rhs_expr |]
          set sql_rhs [:sql_value $rhs]
          #:msg "check op '$op' in SQL [info exists op_map($op,sql)]"
          if {[info exists op_map($op,sql)]} {
            lappend sql_clause [subst -nocommands $op_map($op,sql)]
            if {[info exists :db_slot($lhs_var)]} {
              set lhs_var "\[set :$lhs_var\]"
              lappend tcl_clause [subst -nocommands $op_map($op,tcl)]
            } else {
              :msg "ignoring unknown variable '$lhs_var' in expression (have '[lsort [array names :db_slot]]')"
            }
          } elseif {[llength $rhs]>1} {
            lappend sql_clause "$lhs_var in ([ns_dbquotelist $rhs])"
            # the following statement is only needed, when we rely on tcl-only
            lappend tcl_clause "\[lsearch -exact {$rhs} \[:property $lhs\]\] > -1"
          } else {
            lappend sql_clause "$lhs_var $sql_op($op) '$rhs'"
            # the following statement is only needed, when we rely on tcl-only
            lappend tcl_clause "\[:property $lhs\] $tcl_op($op) {$rhs}"
          }
        } else {
          #
          # Field names referring to instance attributes.
          #
          set hleft [::xowiki::hstore::double_quote $lhs]
          lappend vars $lhs ""
          if {$op eq "contains"} {
            #make approximate query
            set lhs_var instance_attributes
            set sql_rhs $rhs_expr
            lappend sql_clause [subst -nocommands $op_map($op,sql)]
          }
          set lhs_var "\[dict get \$__ia $lhs\]"
          set tcl_rhs_clauses {}
          foreach rhs [split $rhs_expr |] {
            set sql_rhs [:sql_value $rhs]
            if {[info exists op_map($op,tcl)]} {
              lappend tcl_rhs_clauses [subst -nocommands $op_map($op,tcl)]
            } else {
              lappend tcl_rhs_clauses "$lhs_var $tcl_op($op) {$rhs}"
            }
            if {$op eq "="} {
              # TODO: think about a solution for other operators with
              # hstore maybe: extracting it by a query via hstore and
              # compare in plain SQL
              lappend h_clause "$hleft=>[::xowiki::hstore::double_quote $rhs]"
            }
          }
          lappend tcl_clause ([join $tcl_rhs_clauses ||])
        }
      } else {
        :msg "ignoring $clause"
      }
    }
    if {[llength $tcl_clause] == 0} {
      set tcl_clause [list true]
    }
    #:msg sql=$sql_clause,tcl=$tcl_clause
    set result [list  tcl [join $tcl_clause $logical_op]  h [join $h_clause ,]  vars $vars  sql $sql_clause]
    #:msg "filter_expression -sql $sql inp '$input_expr' log '$logical_op' -> $result"
    
    return $result
  • get_all_children (scripted)

     xowiki::FormPage[i] get_all_children

    Testcases:
    includelet_childresources, xowiki
    
    set folder [::xo::db::CrClass get_instance_from_db -item_id $folder_id -revision_id 0]
    set package_id [$folder package_id]
    
    set publish_status_clause [::xowiki::Includelet publish_status_clause $publish_status]
    set result [::xo::OrderedComposite new -destroy_on_cleanup]
    $result set folder_ids ""
    
    set list_of_folders [list $folder_id]
    set inherit_folders [FormPage get_super_folders $package_id $folder_id]
    #:log inherit_folders=$inherit_folders
    
    foreach item_ref $inherit_folders {
      set folder [::xo::cc cache [list ::$package_id get_page_from_item_ref $item_ref]]
      if {$folder eq ""} {
        ad_log error "Could not resolve parameter folder page '$item_ref' of FormPage [self]."
      } else {
        lappend list_of_folders [$folder item_id]
      }
    }
    
    if {$include_child_folders eq "direct"} {
      #
      # Get all children of the current folder on the first level and
      # append it to the list_of_folders.
      #
      set folder_form [::$package_id instantiate_forms -forms en:folder.form]
      set child_folders [xo::dc list -prepare integer,integer get_child_folders {
        select item_id from xowiki_form_instance_item_index
        where parent_id = :folder_id
        and page_template = :folder_form
      }]
      foreach f $child_folders {
        ::xo::db::CrClass get_instance_from_db -item_id $f
      }
      lappend list_of_folders {*}$child_folders
    }
    
    $result set folder_ids $list_of_folders
    
    foreach folder_id $list_of_folders {
      foreach object_type $object_types {
        set attributes [list revision_id creation_user title parent_id page_order  "to_char(last_modified,'YYYY-MM-DD HH24:MI') as last_modified" ]
        set base_table [$object_type set table_name]i
        if {$object_type eq "::xowiki::FormPage"} {
          set attributes "bt.* $attributes"
        }
        set items [$object_type get_instances_from_db  -folder_id $folder_id  -with_subtypes false  -initialize $initialize  -select_attributes $attributes  -where_clause "$extra_where_clause $publish_status_clause"  -base_table $base_table]
    
        foreach i [$items children] {
          $result add $i
        }
      }
    }
    return $result

Methods (to be applied on instances)

  • combine_data_and_form_field_default (scripted, public)

     <instance of xowiki::FormPage[i]> combine_data_and_form_field_default \
        is_new form_field data_value

    Combine the value of the form field (e.g. determined by the default) with the value in the instance attributes. This function decides, whether it should honor the data value or the form field value for e.g. rendering forms.

    Parameters:
    is_new (required)
    is this a new entry?
    form_field (required)
    object id of the form field
    data_value (required)
    the data from the instance attributes.

    Testcases:
    create_form_with_form_instance
    set form_field_value [$form_field value]
    if {$is_new && $form_field_value ne "" && $data_value eq ""} {
      #
      # On fresh entries, take the default value in case the old
      # value is blank.
      #
    } else {
      #
      # Reset for form field value to the external
      # representation of the data value.
      #
      $form_field value [$form_field convert_to_external $data_value]
    }
    #ns_log notice "combine_data_and_form_field_default $is_new form_field [$form_field name] data_value <$data_value> final <[$form_field value]>"
  • extra_html_fields (scripted, public)

     <instance of xowiki::FormPage[i]> extra_html_fields

    Should be overloaded to provide extra content to some forms. This method can be used to add additional (e.g. hidden) HTML input fields to form pages. Example: ::html::input -type hidden -name __object_name -value [::security::parameter::signed ${:name}]

    Testcases:
    create_test_items
    return ""
  • get_property (scripted, public)

     <instance of xowiki::FormPage[i]> get_property [ -source source ] \
        -name name  [ -default default ]

    Retrieves a FormPage property

    Switches:
    -source (optional)
    page name to be resolved and used instead this FormPage to fetch the property
    -name (required)
    -default (optional)

    Testcases:
    create_test_items
    if {![info exists source]} {
      set page [self]
    } else {
      set page [:resolve_included_page_name $source]
    }
    return [$page property $name $default]
  • is_folder_page (scripted, public)

     <instance of xowiki::FormPage[i]> is_folder_page \
        [ -include_folder_links include_folder_links ]

    Check, if FormPage is a folder. A FormPage is a folder when its page template is the folder.form or if this is a link pointing to a folder.

    Switches:
    -include_folder_links (optional, defaults to "true")
    return true, if the current page is a link to a folder.
    Returns:
    boolean

    Testcases:
    xowiki_test_cases
    #
    # Make sure, the page_template is instantiated
    #
    if {![nsf::is object ::${:page_template}]} {
      ::xo::db::CrClass get_instance_from_db -item_id ${:page_template}
    }
    set page_template_name [${:page_template} name]
    if {$page_template_name eq "en:folder.form"} {
      return 1
    } elseif {$include_folder_links && $page_template_name eq "en:link.form"} {
      set link_type [:get_property_from_link_page link_type]
      return [expr {$link_type eq "folder_link"}]
    } else {
      return 0
    }
  • load_values_into_form_fields (scripted, public)

     <instance of xowiki::FormPage[i]> load_values_into_form_fields \
        form_fields

    Load either the instance variables or the instance attributes into the provided form-fields. The function sets the values based on the default values and the values for the current object.

    Parameters:
    form_fields (required)

    Testcases:
    create_form_with_form_instance, create_form_with_numeric
    set is_new [:is_new_entry ${:name}]
    
    foreach f $form_fields {
      set att [$f name]
      switch -glob $att {
        __* {}
        _* {
          set varname [string range $att 1 end]
          :combine_data_and_form_field_default $is_new $f [set :$varname]
        }
        default {
          #:log "load_values_into_form_field $att"  "exists [dict exists ${:instance_attributes} $att]"  "in [dict keys ${:instance_attributes}]"
          if {[dict exists ${:instance_attributes} $att]} {
            :combine_data_and_form_field_default $is_new $f [dict get ${:instance_attributes} $att]
          }
        }
      }
    }
  • property (scripted, public)

     <instance of xowiki::FormPage[i]> property name [ default ]

    Retrieve a FormPage property.

    Parameters:
    name (required)
    property name. Names starting with _ refer to object's members, rather than instance attributes.
    default (optional)
    fallback value when property is not set.

    Testcases:
    xowiki_test_cases
    if {[regexp {^_([^_].*)$} $name _ varname]} {
      if {[info exists :$varname]} {
        return [set :$varname]
      }
    } elseif {[dict exists ${:instance_attributes} $name]} {
      return [dict get ${:instance_attributes} $name]
    }
    return $default
  • render_thumbnails (scripted, public)

     <instance of xowiki::FormPage[i]> render_thumbnails upload_info

    Renderer of the thumbnail file(s). This method is a stub to be refined (e.g. in xowf).

    Parameters:
    upload_info (required)
    dict containing the "file_object" and "file_name"
    Returns:
    HTML content

    Testcases:
    No testcase defined.
    return "[dict get $upload_info file_name] created"
  • set_form_data (scripted, public)

     <instance of xowiki::FormPage[i]> set_form_data form_fields

    Store the instance attributes or default values into the form via set_form_value. This function iterates over the provided form-fields and checks, if these are known fields in the current form. These known field names are defined via the method "field_names" that extracts these names from a form. If one wants to load all values from an FormPage into the provided form-fields, use method "load_values_into_form_fields" instead.

    Parameters:
    form_fields (required)

    Testcases:
    create_form_with_form_instance
    ::xo::require_html_procs
    
    foreach f $form_fields {
      set att [$f name]
      # just handle fields of the form entry
      if {![dict exists ${:__field_in_form} $att]} continue
      #:msg "set form_value to form-field $att [dict exists ${:instance_attributes} $att]"
      if {[dict exists ${:instance_attributes} $att]} {
        #:msg "my set_form_value from ia $att '[dict get ${:instance_attributes} $att]', external='[$f convert_to_external [dict get ${:instance_attributes} $att]]' f.value=[$f value]"
        :set_form_value $att [$f convert_to_external [dict get ${:instance_attributes} $att]]
      } else {
        # do we have a value in the form? If yes, keep it.
        set form_value [:get_form_value $att]
        #:msg "no instance attribute, set form_value $att '[$f value]' form_value=$form_value"
        if {$att eq ""} {
          # we have no instance attributes, use the default value from the form field
          :set_form_value $att [$f convert_to_external [$f value]]
        }
      }
    }
  • set_live_revision (scripted, public)

     <instance of xowiki::FormPage[i]> set_live_revision \
        -revision_id revision_id  [ -publish_status publish_status ]
    Switches:
    -revision_id (required)
    -publish_status (optional, defaults to "ready")
    one of 'live', 'ready' or 'production'

    Testcases:
    create_folder_with_page, create_workflow_with_instance
    next
    
    # Fetch fresh instance from db so that we have actual values
    # from the live revision for the update of the item_index.
    
    set page [::xo::db::CrClass get_instance_from_db -revision_id $revision_id]
    $page publish_status $publish_status
    $page update_item_index
  • set_property (scripted, public)

     <instance of xowiki::FormPage[i]> set_property [ -new new ] name \
        value

    Stores a value as FormPage property

    Switches:
    -new (optional, defaults to "0")
    boolean flag telling if the property is new. Setting a value on a non-existing property without specifying this flag will result in an error.
    Parameters:
    name (required)
    property name. Names starting with _ indicate an object variable rather than a property stored in instance_attributes
    value (required)
    property value
    Returns:
    value (eventually converted to a has-notation message key)

    Testcases:
    create_form_with_form_instance
    if {[string match "_*" $name]} {
      set key [string range $name 1 end]
    
      if {!$new && ![info exists :$key]} {
        error "property '$name' ($key) does not exist.  you might use flag '-new 1' for set_property to create new properties"
      }
      set :$key $value
    
    } else {
    
      if {!$new && ![dict exists ${:instance_attributes} $name]} {
        error "property '$name' does not exist.  you might use flag '-new 1' for set_property to create new properties"
      }
      dict set :instance_attributes $name $value
    }
    return $value
  • update_attribute_from_slot (scripted, public)

     <instance of xowiki::FormPage[i]> update_attribute_from_slot \
        [ -revision_id revision_id ] slot value

    Tailored version of update_attribute_from_slot to keep insert_xowiki_form_instance_item_index in sync after single attribute updates.

    Switches:
    -revision_id (optional)
    Parameters:
    slot (required, object)
    value (required)

    Testcases:
    slot_interactions
    #
    # Perform first the regular operations.
    #
    next
    #
    # Make sure to update update_item_index when the attribute is
    # contained in the xowiki_form_instance_item_index.
    #
    set colName [$slot column_name]
    
    if {$colName in {
      package_id
      parent_id
      publish_status
      page_template
      assignee
      state
    }} {
      ::xowiki::update_item_index -item_id ${:item_id} -$colName $value
    } elseif {
              $colName eq "instance_attributes"
              && [::xo::dc has_hstore]
              && [::${:package_id} get_parameter use_hstore:boolean 0]
            } {
      ::xowiki::update_item_index -item_id ${:item_id} -hstore_attributes $value
    }
  • update_item_index (scripted, public)

     <instance of xowiki::FormPage[i]> update_item_index

    Tailored version of CrItem.update_item_index to keep insert_xowiki_form_instance_item_index in sync after updates.

    Testcases:
    link_tests
    :instvar name item_id package_id parent_id publish_status  page_template instance_attributes assignee state
    
    set useHstore [::$package_id get_parameter use_hstore:boolean 0]
    set updateVars {name = :name, package_id = :package_id,
      parent_id = :parent_id, publish_status = :publish_status,
      page_template = :page_template, assignee = :assignee,
      state = :state}
    
    if {$useHstore} {
      set hkey [::xowiki::hstore::dict_as_hkey [:hstore_attributes]]
      append updateVars ", hkey = '$hkey'"
    }
    
    set rows [xo::dc dml update_xowiki_form_instance_item_index [subst {
      update xowiki_form_instance_item_index
      set $updateVars
      where item_id = :item_id
    }]]
    
    if {$rows ne "" && $rows < 1} {
      set insertVars {item_id, name, package_id, parent_id, publish_status,
        page_template, assignee, state
      }
      set insertValues {:item_id, :name, :package_id, :parent_id, :publish_status,
        :page_template, :assignee, :state
      }
      if {$useHstore} {
        append insertVars {, hkey}
        append insertValues ", '$hkey'"
      }
    
      ::xo::dc dml insert_xowiki_form_instance_item_index [subst {
        insert into xowiki_form_instance_item_index
        ($insertVars) values ($insertValues)
      }]
    }
  • www-edit (scripted, public)

     <instance of xowiki::FormPage[i]> www-edit \
        [ -validation_errors validation_errors ] \
        [ -disable_input_fields disable_input_fields ] [ -view on|off ]

    This web-callable method renders a form page in "edit" mode (i.e. provide input fields). The following query parameters can be used to influene the results "return_url", "title", "detail_link", "text", and "description".

    Switches:
    -validation_errors (optional)
    -disable_input_fields (optional, defaults to "0")
    -view (optional, boolean, defaults to "true")

    Testcases:
    create_workflow_with_instance
    #:log "edit [self args]"
    
    :include_header_info -prefix form_edit
    if {[::xo::cc mobile]} {
      :include_header_info -prefix mobile
    }
    
    set form [:get_form]
    set anon_instances [:get_anon_instances]
    #:log form=$form
    #:log anon_instances=$anon_instances
    
    set field_names [:field_names -form $form]
    #:log field_names=$field_names
    set form_fields [:create_form_fields $field_names]
    #foreach f0 $form_fields {
    #  ns_log notice "... created ff [$f0 name] [$f0 info class] '[$f0 value]'"
    #}
    
    if {$form eq ""} {
      #
      # Since we have no form, we create it on the fly
      # from the template variables and the form field specifications.
      #
      set form "<form></form>"
      set formgiven 0
    } else {
      set formgiven 1
    }
    #:log formgiven=$formgiven
    
    # check name field:
    #  - if it is for anon instances, hide it,
    #  - if it is required but hidden, show it anyway
    #    (might happen, when e.g. set via @cr_fields ... hidden)
    set name_field [:lookup_form_field -name _name $form_fields]
    
    if {$anon_instances} {
      #$name_field config_from_spec hidden
    } else {
      if {[$name_field istype ::xowiki::formfield::hidden]
          && [$name_field required] == true
        } {
        $name_field config_from_spec text,required
        $name_field type text
      }
    }
    
    #
    # Include _text only, if explicitly needed (in form
    # needed(_text))".
    #
    if {![dict exists ${:__field_needed} _text]} {
      #:msg "setting text hidden"
      set f [:lookup_form_field -name _text $form_fields]
      $f config_from_spec hidden
    }
    
    if {[:exists_form_parameter __disabled_fields]} {
      #
      # Disable some form-fields since these are disabled in the form
      # as well.
      #
      foreach name [:form_parameter __disabled_fields:0..n] {
        set f [:lookup_form_field -name $name $form_fields]
        $f set_disabled true
      }
    }
    
    #:show_fields $form_fields
    #:log "__form_action [:form_parameter __form_action {}]"
    
    if {[:form_parameter __form_action ""] eq "save-form-data"} {
      #
      # We want to save the form data, so we have to validate.
      #
      #:log "we have to validate"
      #
      # In case we are triggered internally, we might not have a
      # a connection. Therefore, do not validate the CSRF token.
      #
      if {![::${:package_id} exists __batch_mode]} {
        security::csrf::validate
      }
    
      lassign [:get_form_data $form_fields] validation_errors category_ids
      if {$validation_errors != 0} {
        #
        # We have validation errors.
        #
        #:log "$validation_errors validation errors in $form_fields"
        #foreach f $form_fields { :log "$f: [$f name] '[$f set value]' err: [$f error_msg] " }
        #
        # In case we are triggered internally, we might not have a
        # a connection, so we don't present the form with the
        # error messages again, but we return simply the validation
        # problems.
        #
        if {[::${:package_id} exists __batch_mode]} {
          set errors [list]
          foreach f $form_fields {
            if {[$f error_msg] ne ""} {
              lappend errors [list field [$f name] value [$f set value] error [$f error_msg]]
            }
          }
          set evaluation_errors ""
          if {[::${:package_id} exists __evaluation_error]} {
            set evaluation_errors "\nEvaluation error: [::${:package_id} set __evaluation_error]"
            ::${:package_id} unset __evaluation_error
          }
          error "[llength $errors] validation error(s): $errors $evaluation_errors"
        }
        #
        # Reset the name in error cases to the original one.
        #
        set :name [:form_parameter __object_name:signed,convert]
      } else {
        #
        # We have no validation errors, so we can save the content.
        #
        :save_data  -use_given_publish_date [expr {"_publish_date" in $field_names}]  [::xo::cc form_parameter __object_name:signed,convert ""$category_ids
    
        #
        # The data might have references. Perform the rendering here to compute
        # the references instead on every view (which would be safer, but slower). This is
        # roughly the counterpart to edit_data and save_data in ad_forms.
        #
        set content [:render -update_references all]
        #:log "after save refs=[expr {[info exists :references]?${:references} : {NONE}}]"
    
        set redirect_method [:form_parameter __form_redirect_method:wordchar "view"]
        #:log "redirect_method $redirect_method"
    
        if {$redirect_method eq "__none"} {
          return
        } else {
          if {$redirect_method ne "view"} {
            set qp "?m=$redirect_method"
          } else {
            set qp ""
          }
          set url [:pretty_link]$qp
          #
          # The method query_parameter uses now "::xo::cc set_parameter ...."
          # with highest precedence
          #
          set return_url [::${:package_id} query_parameter return_url:localurl $url]
    
          #:log "${:name}: url=$url, return_url=$return_url"
          ::${:package_id} returnredirect $return_url
    
          return
        }
      }
    } elseif {[:form_parameter __form_action ""] eq "view-form-data"
              && ![info exists :__feedback_mode]
            } {
      #
      # We have nothing to save (maybe everything is read-only). Check
      # __feedback_mode to prevent recursive loops.
      #
      set redirect_method [:form_parameter __form_redirect_method:wordchar "view"]
      #:log "__redirect_method=$redirect_method"
      return [:www-view]
    } else {
      #
      # Build the input form and display the current values.
      #
      #:log "form_action is something different: <[:form_parameter __form_action {}]>"
      if {[:is_new_entry ${:name}]} {
        set :creator [::xo::get_user_name [::xo::cc user_id]]
        set :nls_language [::${:package_id} default_locale]
      }
    
      #array set __ia ${:instance_attributes}
      :load_values_into_form_fields $form_fields
    
      foreach f $form_fields {
        set ff([$f name]) $f
      }
    
      #
      # For named entries, just set the entry fields to empty,
      # without changing the instance variables
      #
      #:log "my is_new_entry ${:name} = [:is_new_entry ${:name}]"
      if {[:is_new_entry ${:name}]} {
    
        if {$anon_instances} {
          set basename [::xowiki::autoname basename [${:page_template} name]]
          set name [::xowiki::autoname new -name $basename -parent_id ${:parent_id}]
          #:log "generated name=$name, page_template-name=[${:page_template} name]"
          $ff(_name) value $name
        } else {
          $ff(_name) value [$ff(_name) default]
        }
        if {![$ff(_title) istype ::xowiki::formfield::hidden]} {
          $ff(_title) value [$ff(_title) default]
        }
        foreach param [list title detail_link:localurl text description] {
          regexp {^([^:]+):?} $param . var
          if {[:exists_query_parameter $var]} {
            set value [:query_parameter $param]
            switch -- $var {
              detail_link {
                set f [:lookup_form_field -name $var $form_fields]
                $f value [$f convert_to_external $value]
              }
              title - text - description {
                set f [:lookup_form_field -name _$var $form_fields]
              }
            }
            $f value [$f convert_to_external $value]
          }
        }
      }
    
      $ff(_name) set transmit_field_always 1
      $ff(_nls_language) set transmit_field_always 1
    }
    
    #
    # Some final sanity checks.
    #
    :form_fields_sanity_check $form_fields
    :post_process_form_fields $form_fields
    
    #
    # "dom parse -html" has two problems with ADP tags like "<adp:icon ...>":
    # a) If the tag name contains a colon or underscore, the tag is
    #    treated like plain text, i.e. "<" and ">" are converted into
    #    HTML entities.
    # b) These tags have to be closed "<adp:icon ...>" is invalid.
    #    Several existomg ADP tags have not closing tag.
    #
    # Therefore, we resolve the ADP tags before parsing the text by
    # tdom. There should be some framework support to do this in
    # general, but until we have this, resolve this problem here locally.
    #
    set form [::template::adp_parse_tags [:substitute_markup $form]]
    
    #
    # The following command would be correct, but does not work due to a bug in
    # tdom.
    # set form [:regsub_eval   #              [template::adp_variable_regexp] $form  #              {:form_field_as_html -mode edit "\\\1" "\2" $form_fields}]
    # Due to this bug, we program around and replace the at-character
    # by \x03 to avoid conflict with the input and we replace these
    # magic chars finally with the fields resulting from tdom.
    
    set form [string map [list @ \x03] $form]
    #:msg form=$form
    
    dom parse -html -- $form :doc
    ${:doc} documentElement :root
    
    if {${:root} eq ""} {
      error "form '$form' is not valid"
    }
    
    ::xo::require_html_procs
    ${:root} firstChild fcn
    #:msg "orig fcn $fcn, root ${:root} [${:root} nodeType] [${:root} nodeName]"
    
    set formNode [lindex [${:root} selectNodes //form] 0]
    if {$formNode eq ""} {
      :msg "no form found in page [${:page_template} name]"
      ns_log notice "no form found in page [${:page_template} name]\n$form"
      set rootNode ${:root}
      $rootNode firstChild fcn
    } else {
      set rootNode $formNode
      $rootNode firstChild fcn
      # Normally, the root node is the formNode, fcn is the first
      # child (often a TEXT_NODE), but ic can be even empty.
    }
    
    
    #
    # Prepend some fields above the HTML contents of the form.
    #
    $rootNode insertBeforeFromScript {
      ::html::div {
        ::html::input -type hidden -name __object_name -value [::security::parameter::signed ${:name}]
        ::html::input -type hidden -name __form_action -value save-form-data
        ::html::input -type hidden -name __current_revision_id -value ${:revision_id}
        :extra_html_fields
        ::html::CSRFToken
      }
      #
      # Insert automatic form fields on top.
      #
      foreach att $field_names {
        #if {$formgiven && ![string match _* $att]} continue
        if {[dict exists ${:__field_in_form} $att]} continue
        set f [:lookup_form_field -name $att $form_fields]
        #:log "insert auto_field $att $f ([$f info class])"
        $f render_item
      }
    } $fcn
    #
    # Append some fields after the HTML contents of the form.
    #
    set button_class(wym) ""
    set button_class(xinha) ""
    set has_file 0
    $rootNode appendFromScript {
      # append category fields
      foreach f $form_fields {
        #:msg "[$f name]: is wym? [$f has_instance_variable editor wym]"
        if {[string match "__category_*" [$f name]]} {
          $f render_item
        } elseif {[$f has_instance_variable editor wym]} {
          set button_class(wym) "wymupdate"
        } elseif {[$f has_instance_variable editor xinha]} {
          set button_class(xinha) "xinhaupdate"
        }
        if {[$f has_instance_variable type file]} {
          set has_file 1
        }
      }
    
      #
      # Add a submit field(s) at bottom.
      #
      :render_form_action_buttons -CSSclass [string trim "$button_class(wym) $button_class(xinha)"]
    }
    
    if {$formNode ne ""} {
    
      if {[:exists_query_parameter "return_url"]} {
        set return_url [:query_parameter return_url:localurl]
      } else {
        #
        # When no return_url is specified and we edit a page different
        # from the invoked page, we use the calling page for default
        # redirection.  We do not want to redirect to some "embedded"
        # object after the edit. This happens if one edits e.g. a page
        # through a link.
        #
        if {[::xo::cc exists invoke_object]
            && [::xo::cc invoke_object] ne [self]
          } {
          #:log "=== no return_url specified, using [::xo::cc url] or [[::${:package_id} context] url]"
          set return_url [::xo::cc url]
          set return_url [ad_urlencode_url $return_url]
        }
      }
      set m [:form_parameter __form_redirect_method:wordchar "edit"]
      set url [export_vars -no_base_encode -base [:action_url] {m return_url}]
      #:log "=== setting action <$url> for form-action my-name ${:name}"
      $formNode setAttribute action $url method POST role form
      if {$has_file} {$formNode setAttribute enctype multipart/form-data}
      Form add_dom_attribute_value $formNode class [${:page_template} css_class_name]
    }
    
    :set_form_data $form_fields
    if {$disable_input_fields} {
      #
      # (a) Disable explicit input fields.
      #
      foreach f $form_fields {$f set_disabled true}
      #
      # (b) Disable input in HTML-specified fields.
      #
      set disabled [Form dom_disable_input_fields $rootNode]
      #
      # Collect these variables in a hidden field to be able to
      # distinguish later between e.g. un unchecked checkmark and an
      # disabled field. Maybe, we have to add the fields from case (a)
      # as well.
      #
      $rootNode appendFromScript {
        ::html::input -type hidden -name "__disabled_fields" -value $disabled
      }
    }
    :post_process_dom_tree ${:doc} ${:root} $form_fields
    
    set html [${:root} asHTML]
    set html [:regsub_eval   {(^|[^\\])\x03([[:alnum:]_:]+)\x03} $html  {:form_field_as_html -mode edit "\\\1" "\2" $form_fields}]
    #
    # Replace unbalanced @ characters.
    #
    set html [string map [list \x03 @] $html]
    
    #
    # Handle unreported errors (in the future...). Unreported errors
    # might occur, when a form-field was rendered above without
    # "render_item". This can happen with inline rendering of the
    # input fields where validation errors occur. Inline rendering
    # happens very seldom (I know not a single occurrence in the
    # wild). For such cases, one should define an extra field in the
    # form with an idea, reparse the tree and insert the errors
    # there. But first look, if we find a single occurrence.
    #
    set unprocessed {}
    foreach f $form_fields {
      if {[$f set error_msg] ne ""
          && ![$f exists error_reported]
        } {
        ns_log notice "form-field [$f name] has unprocessed error msg '[$f set error_msg]'"
        #$f render_error_msg
        lappend unprocessed [$f name]
      }
    }
    #ns_log notice "=============== $unprocessed unprocessed error messages"
    if {[llength $unprocessed] > 0} {
      ad_log warning "form has [llength $unprocessed] unprocessed "  "error messages in fields $unprocessed"
    }
    
    #:log "calling VIEW with HTML [string length $html]"
    if {$view} {
      :www-view $html
    } else {
      return $html
    }
  • www-file-upload (scripted, public)

     <instance of xowiki::FormPage[i]> www-file-upload

    This web-callable method can be used for uploading files using the current object as parent object for the new content. This method is typically called via drop-zone in a POST request, where the FormPage is a folder (which is treated as parent object)

    Testcases:
    No testcase defined.
    
    if {[ns_conn method] ne "POST"} {
      error "method should be called via POST"
    }
    
    #
    # Get the disposition via query parameter.  We have currently the
    # following disposition classes defined (see
    # xowiki-uploader-procs.tcl)
    #
    #   - ::xowiki::UploadFile
    #   - ::xowiki::UploadPhotoForm
    #   - ::xowiki::UploadFileIconified
    #
    ::security::csrf::validate
    
    set disposition [:query_parameter disposition:wordchar File]
    
    #
    # Filename is sanitized. If the filename contains only invalid
    # characters, "ad_sanitize_filename" might return empty, and we
    # complain.
    #
    set fileName [ad_sanitize_filename  [ns_queryget name [ns_queryget upload]]]
    if {[string length $fileName] == 0} {
      ad_return_complaint 1 [_ acs-templating.Invalid_filename]
      ad_script_abort
    }
    
    set dispositionClass ::xowiki::UploadFile
    if {[info commands ::xowiki::Upload$disposition] ne ""} {
      set dispositionClass ::xowiki::Upload$disposition
    }
    
    #ns_log notice "disposition class '$dispositionClass'"
    set dispositionObject [$dispositionClass new  -file_name $fileName  -content_type [ns_queryget upload.content-type]  -tmpfile [ns_queryget upload.tmpfile]  -parent_object [self]]
    set result [$dispositionObject store_file]
    $dispositionObject destroy
    ns_return [dict get $result status_code] text/plain [dict get $result message]
    ad_script_abort
  • www-toggle-modebutton (scripted, public)

     <instance of xowiki::FormPage[i]> www-toggle-modebutton

    AJAX called function, called via POST. The function toggles the state of a button in the backend. The client provides the name of the button as form field named "button". If none is provided, the button is named as default "admin"

    Testcases:
    No testcase defined.
    #
    # Check, if this function was called via POST
    #
    if {[ns_conn method] ne "POST"} {
      error "method should be called via POST"
    }
    
    #
    # Get the toggle name. Modebuttons are named like:
    #
    #    ::xowiki::mode::admin
    #
    set button [ns_queryget button admin]
    ::xowiki::mode::$button toggle
    ns_return 200 text/plain ok