# -*- Tcl -*- # # Workflow template for answering inclass 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 based on the test-item infrastructure using # the "renaming_form_loader" and "question_manager". # set :autoname 1 ;# to avoid editable name field set :policy ::xowf::test_item::test-item-policy-answer set :debug 0 ######################################################################## # # Properties # # 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 # ip: IP address of the user, kept in the instance attribute for auditing # ######################################################################## Property position -default 0 Property return_url -default "" -allow_query_parameter true Property try_out_mode -default 0 -allow_query_parameter true ######################################################################## # # Action definitions # ######################################################################## Action allocate -proc activate {obj} { # Called, when we try to create or use a workflow instance # via a workflow definition ($obj is a workflow definition) set parent_id [$obj parent_id] set name [ns_md5 $parent_id-[::xo::cc set untrusted_user_id]] set parent_obj [::xo::db::CrClass get_instance_from_db -item_id $parent_id] :payload [list title [$parent_obj title] name $name] } Action initialize -proc activate {obj} { # called, after workflow instance was created # make sure to create the parent (the controlling workflow) set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$obj parent_id]] set parent_state [$parent_obj state] # # Don't allow one to enter values when the state of the parent # workflow is not published (the teacher has not published the exam, # or closed it already). Only allow usage in the try-out-mode. # if {$parent_state ne "published" && [$obj property try_out_mode 0] == 0} { set current_state [$obj property _state] set locking_state [expr {$current_state eq "initial" ? "initial" : "done"}] set locking_msg(initial) "#xowf.online-exam-not-published#" set locking_msg(done) "#xowf.online-exam-finished#" util_user_message -message $locking_msg($locking_state) # # Force the user in the done state. Alternatively, we could # handle this in the provide a different form or push the user to some other state. # [:wf_context] set_current_state $locking_state } } Action instproc goto_page {position} { :set_property position $position } Action instproc set_page {obj increment} { set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$obj parent_id]] set pages [::xowf::test_item::question_manager question_names $parent_obj] set position [:property position 0] incr position $increment if {$position < 0} { set position 0 } elseif {$position >= [llength $pages]} { set position [expr {[llength $pages] - 1}] } :goto_page $position } Action prevQuestion \ -next_state working \ -label #xowf.previous_question# \ -proc activate {obj} {:set_page $obj -1} Action nextQuestion \ -next_state working \ -label #xowf.next_question# \ -proc activate {obj} {:set_page $obj 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 \ -next_state done \ -label #xowf.inclass-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" #ad_returnredirect $return_url #ad_script_abort } Action start \ -next_state working \ -label #xowf.online-exam-start# \ -proc activate {obj} { $obj set_property position 0 } Action start_again \ -label #xowf.first_question# \ -next_state working -proc activate {obj} { $obj set_property position 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 }} } State working \ -form_loader working_form_loader State initial \ -form_loader working_form_loader State done \ -form_loader done_form_loader #-form_loader summary_form ######################################################################## # # Helper methods for the workflow container # ######################################################################## # # Field-renaming form loader # proc working_form_loader {ctx form_name} { #ns_log notice "============ working_form_loader" set obj [$ctx object] # # When we are in the reporting modes return the results from the # "done_form_loader". We could check as well the state oft the # parent workflow, but maybe we want this in more situations. # if {[$obj exists online-exam-userName]} { return [done_form_loader $ctx $form_name] } set item_nr [$obj property position] set parent_id [$obj parent_id] #:msg "working_form_loader [$obj instance_attributes]" set parent_obj [::xo::db::CrClass get_instance_from_db -item_id $parent_id] set parent_state [$parent_obj state] # # In case shuffling is required, fetch via the shuffled position. # set shuffle_id [expr {[$parent_obj property shuffle_items 0] ? [$obj creation_user] : -1}] set position [::xowf::test_item::question_manager shuffled_index \ -shuffle_id $shuffle_id \ $parent_obj $item_nr] #ns_log notice "============ working_form_loader load form on pos $position" # # Load the form. # set form_obj [::xowf::test_item::question_manager nth_question_obj $parent_obj $position] # # 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 -position $position -item_nr $item_nr -for_question -with_minutes return $form_obj } # # Done form loader # proc done_form_loader {ctx form_name} { set obj [$ctx object] # # # if {[$obj exists __feedback_mode] && [$obj set __feedback_mode] > 0} { set container [$ctx wf_container] set result [$container summary_form $ctx $form_name] } else { set result [::xowiki::Form new \ -destroy_on_cleanup \ -set name en:finished \ -form {{

#xowf.inclass-exam-already_answered#

} text/html} \ -text {} \ -anon_instances t \ ] } return $result } # # Set "title" with question/user/IP information. Note that the # "set_title" method is as well responsible for calling the rename # function via question_manager. # :proc set_title { obj -position:integer -item_nr:integer {-for_question:switch false} {-with_minutes:switch false} } { set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$obj parent_id]] if {$for_question && [$obj state] in {initial working}} { set form_info [::xowf::test_item::question_manager nth_question_form \ -with_numbers \ -with_title=false \ -with_minutes=$with_minutes \ -position $position \ -item_nr $item_nr \ $parent_obj] set title_info [lindex [dict get $form_info title_infos] 0] set titleString [dict get $title_info full_title] set title [list [string trim $titleString]] } lappend title \ [$parent_obj title] \ "IP: [$obj property ip]" #ns_log notice "SETTING $obj title [join $title { · }]" $obj title [join $title " · "] set target_time [::xowf::test_item::question_manager exam_target_time -manager $parent_obj -instance $obj] #:msg set_title-set_parameter-MenuBar-[$obj state] ::xo::cc set_parameter MenuBar 0 ::xo::cc set_parameter top_includelet [list countdown-timer -target_time $target_time] ::xo::cc set_parameter template_file view-plain-master } # # 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] set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$obj parent_id]] #:msg "summary_form_loader $form_title [$obj instance_attributes]" set shuffle_id [expr {[$parent_obj property shuffle_items 0] ? [$obj creation_user] : -1}] set form_info [::xowf::test_item::question_manager combined_question_form \ -with_numbers \ -with_title \ -with_minutes \ -shuffle_id $shuffle_id \ $parent_obj] set summary_form [dict get $form_info form] set summary_fc [dict get $form_info disabled_form_constraints] regsub -all {]*>} $summary_form {} summary_form :set_title $obj return [::xowiki::Form new \ -destroy_on_cleanup \ -name en:summary \ -title $form_title \ -form [list
$summary_form
text/html] \ -text {} \ -anon_instances t \ -form_constraints $summary_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. # set ctx [:wf_context] set container [$ctx wf_container] ${container}::Property ip -default [expr {[ns_conn isconnected] ? [ad_conn peeraddr] : "nowhere"}] #:log "inclass-exam-answer state ${:state}" set ctx [:wf_context] set container [$ctx wf_container] if {$ctx ne $container} { $ctx forward working_form_loader $container %proc $ctx $ctx forward done_form_loader $container %proc $ctx $ctx forward summary_form $container %proc $ctx } set :policy ::xowf::test_item::test-item-policy1 if {${:state} in {initial working done}} { set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [:parent_id]] set question_names [::xowf::test_item::question_manager question_names $parent_obj] # # Use the current_position in the sense of the nth question of the # user, which is not necessarily the nth question in the list of # questions due to shuffling. # set current_position [:property position] set actions {} #if {$current_position > 0 && ${:state} eq "working"} { # lappend actions prevQuestion #} if {${:state} ne "done"} { set count 0 foreach question $question_names { incr count ${container}::Action create ${container}::$count \ -label "$count" \ -next_state working \ -extra_css_class [expr {$current_position == $count - 1 ? "current" : ""}] \ -proc activate {obj} \ [list :goto_page [expr {$count -1}]] lappend actions $count } if { ${:state} in {initial working} && [::xowf::test_item::question_manager more_ahead -position $current_position $parent_obj] } { lappend actions nextQuestion } if {${:state} in {initial working} } { lappend actions save } lappend actions logout } ${container}::${:state} actions $actions } } # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: