# -*- Tcl -*- # # Workflow template for answering online exams. The workflow is # typically controlled from a parent workflow that a teacher can use # to create the exam, to try it out and to publish it # (online-exam.wf). # # This workflow is similar to the classical "iterate.wf" but is more # configurable (the answer forms are passed via template # variables). The workflow uses a form-loader which renames the input # fields to avoid potential name clashes. # # Template variables: # @wfTitle@ # @wfQuestionNames@ # @wfQuestionTitles@ # @wfID@ set :autoname 1 set :debug 1 set pages [list @wfQuestionNames@] set titles [list @wfQuestionTitles@] ######################################################################## # # Properties # # pages: the form pages used as in/out forms # position: the current page in the exam # return_url: when the exam is finished, the user proceeds to this url # try_out_mode: a teacher can try the exam in this mode # current_form: used internally to keep the current form # ip: IP address of the user, kept in the instance attribute for auditing # ######################################################################## Property pages -default $pages Property titles -default $titles Property position -default 0 -allow_query_parameter true Property return_url -default "" -allow_query_parameter true Property try_out_mode -default 0 -allow_query_parameter true Property current_form -default "" Condition more_ahead \ -expr {[$obj property position] < [llength [$obj property pages]]-1} Condition more_before \ -expr {[$obj property position] > 0} set page_count 1 set page_actions {} foreach page $pages { Action $page_count \ -next_state working \ -label "$page_count" \ -proc activate {obj} [list :goto_page [expr {$page_count -1}]] lappend page_actions $page_count incr page_count } ######################################################################## # # Action definitions # ######################################################################## Action allocate -proc activate {obj} { #:msg "allocate $obj" # Called, when we try to create or use a workflow instance # via a workflow definition ($obj is a workflow definition) $obj set_property -new 1 name ___[::xo::cc set untrusted_user_id] } Action initialize -proc activate {obj} { # called, after workflow instance was created $obj set_property -new 1 _title "@wfTitle@" set parent_id [$obj parent_id] #set package_id [$obj package_id] # make sure to create the parent (the controlling workflow) set parent_obj [::xo::db::CrClass get_instance_from_db -item_id $parent_id] set parent_state [$parent_obj state] # # Don't allow one to enter values when the state of the master # workflow is not published (e.g. trial mode, or closed) or when # teacher is not in the try-out-mode. # if {$parent_state ne "published" && [$obj property try_out_mode 0] == 0} { #:msg "LOCKED" set current_state [$obj property _state] set lockin_state [expr {$current_state eq "initial" ? "initial" : "done"}] set lockin_msg(initial) "#xowf.online-exam-not-published#" set lockin_msg(done) "#xowf.online-exam-finished#" util_user_message -message $lockin_msg($lockin_state) # # Force the user in the done state. Alternatively, we could # provide a different form or push the user to some other state. # set ctx [:wf_context] $ctx set_current_state $lockin_state } else { #:msg "not LOCKED" } } Action instproc goto_page {position} { set pages [:property pages] :set_property position $position :set_property current_form [lindex $pages $position] } Action instproc set_page {increment} { set pages [:property pages] set position [:property position 0] incr position $increment if {$position < 0} { set position 0 } elseif {$position >= [llength $pages]} { set position [expr {[llength $pages] - 1}] } :set_property position $position :set_property current_form [lindex $pages $position] } Action prevQuestion \ -next_state working \ -label #xowf.previous_question# \ -proc activate {obj} {:set_page -1} Action nextQuestion \ -next_state working \ -label #xowf.next_question# \ -proc activate {obj} {:set_page 1} Action review \ -next_state done \ -label #xowf.online-exam-review# \ -proc activate {obj} { [[$obj wf_context ] wf_container] addSignature $obj } Action save \ -label #xowf.online-exam-save# Action logout \ -label #xowf.online-exam-submit# \ -proc activate {obj} { [[$obj wf_context ] wf_container] addSignature $obj set pid [$obj package_id] set try_out_mode [$obj property try_out_mode 0] set return_url [$obj property return_url .] #:msg "tryout $try_out_mode return_url $return_url" if {$try_out_mode} { ad_returnredirect $return_url ad_script_abort } else { ::xo::cc set_parameter return_url /register/logout?return_url=$return_url } } Action start \ -next_state working \ -label #xowf.online-exam-start# \ -proc activate {obj} { $obj set_property position 0 $obj set_property current_form [lindex [$obj property pages] 0] } Action start_again \ -label #xowf.first_question# \ -next_state working -proc activate {obj} { $obj set_property position 0 $obj set_property current_form [lindex [$obj property pages] 0] } ######################################################################## # # State definitions # ######################################################################## State parameter { {view_method edit} {extra_js { urn:ad:js:jquery ../file:seal.js?m=download }} {extra_css { /resources/xowf/test-item.css ../file:seal.js?m=download }} {form_loader get_question_form_object} } State working working set page_actions $page_actions working proc actions {} { set actions "" if {[more_before]} {lappend actions prevQuestion} set actions [concat $actions ${:page_actions}] if {[more_ahead]} {lappend actions nextQuestion} lappend actions save review } State initial \ -actions {start logout} \ -form "../en:exam-start" set done_actions [concat $page_actions {start_again logout}] State done \ -actions $done_actions \ -form "../en:exam-done" \ -form_loader summary_form ######################################################################## # # Helper methods for the workflow container # ######################################################################## # # Field-renaming form loader # proc get_question_form_object {ctx form_name} { set obj [$ctx object] # # Load the form. # set form_obj [::xowf::test_item::renaming_form_loader get_form_object $ctx $form_name] # # Update IP address each time the form is loaded. # if {[$obj state] in {"initial" "working"}} { $obj set_property ip [expr {[ns_conn isconnected] ? [ad_conn peeraddr] : "nowhere"}] } # # Update the title of the page # :set_title $obj -question true -minutes [:minutes_string $form_obj] return $form_obj } # # Set "title" with question and user information. # :proc set_title {obj {-question:boolean true} {-minutes ""}} { set t [list] set state [$obj state] set position [$obj property position] if {$question && $state eq "working"} { set titleString "[_ xowf.question] [expr {$position + 1}]: [lindex [$obj property titles] $position]" if {$minutes ne ""} { append titleString " $minutes" } lappend title $titleString } lappend title \ [$obj title] \ "IP: [$obj property ip]" #ns_log notice "SETTING $obj title [join $title { · }] (Container)" $obj title [join $title " · "] } # # Get an attribute of the original question # :proc question_property {form_obj:object attribute {default ""}} { set question [$form_obj get_property -name question] #:msg question=$question if {[dict exists $question question.$attribute]} { set value [dict get $question question.$attribute] } else { set value $default } #:msg "question_property-$attribute='$value' {$question}" return $value } :proc minutes_string {form_obj:object} { set minutes [:question_property $form_obj minutes] if {$minutes ne ""} { set key [expr {$minutes eq "1" ? [_ xowiki.minute] : [_ xowiki.minutes]}] set minutes "($minutes $key)" } } # # Form loader for summary (shows all submission data of a user) # # This form loader is also called indirectly by www-print-answers of # oneline-exam.wf # :proc summary_form {ctx form_title} { set obj [$ctx object] #:msg "summary_form_loader $form_title [$obj instance_attributes]" set summary_form "" set fc {} set counter 0 foreach form_name [$obj property pages] { set form_obj [::xowf::test_item::renaming_form_loader get_form_object $ctx $form_name] set title [lindex [$obj property titles] $counter] set minutes [:minutes_string $form_obj] append summary_form \ "

[_ xowf.question] [incr counter]: $title $minutes

" \n \ [$form_obj property form] \ \n
\n lappend fc {*}[$form_obj property disabled_form_constraints] } :set_title $obj -question 0 # get rid of all inner forms regsub -all {]*>} $summary_form {} summary_form return [::xowiki::Form new \ -name en:summary \ -title "Summary" \ -form [list
$summary_form
text/html] \ -text {} \ -anon_instances t \ -form_constraints [lsort -unique $fc]] } :proc addSignature {obj} { set answerAttributes [xowf::test_item::renaming_form_loader \ answer_attributes [$obj instance_attributes]] set sha256 [ns_md string -digest sha256 $answerAttributes] $obj set_property -new true signature $sha256 return $sha256 } ######################################################################## # # Object specific operations # ######################################################################## :object-specific { # # Ensure default value is updated for each instance individually. # Property ip -default [expr {[ns_conn isconnected] ? [ad_conn peeraddr] : "nowhere"}] set ctx [:wf_context] set container [$ctx wf_container] if {$ctx ne $container} { $ctx forward get_question_form_object $container %proc $ctx $ctx forward summary_form $container %proc $ctx } set working_state_object [$ctx wf_definition_object working] $working_state_object set form [:property current_form] # fallback if the current_form isn't set if {[$working_state_object set form] eq ""} { $working_state_object set form [lindex [:property pages] 0] } } # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: