# -*- Tcl -*- ######################################################################## # Online-Exam workflow, designed similar to mobile-clicker # ======================================================== # # This workflow lets a teacher choose from a predefined set of exam # questions, which are typically open text questions. The teacher # selects one or several exam question via drag and drop. The teacher # can test the exam by entering test answers. The results are provided # in form of a table. # # When the teacher is satisfied with the exam, the exam can be # published. In this step, all answers of the testing phase are # deleted. In the process of publishing, the link to start the exam is # offered to the user. When the exam is published, the teacher can # see the incoming answers in the report by refreshing the page. When # the exam is done, it is unpublished. The workflow offers the teacher # to see a summary of the results in form of a table (an to download # the results via csv), or the teacher can produce a printer friendly # version of the answers. # # You might with to add the following entries to the folder to ease # creation of exercises and exams # # {entry -name New.App.TextInteraction -label "Text Interaction" -form en:TestItemText.form} # {entry -name New.App.TextEntryInteraction -label "Text Entry Interaction" -form en:TestItemTextEntry.form} # {entry -name New.App.MCInteraction -label "MC Interaction" -form en:TestItemMC.form} # {entry -name New.App.Exam -label Exam -form en:online-exam.wf} # # Gustaf Neumann, Feb 2012 ######################################################################## set :autoname 1 set :debug 1 #set :masterWorkflow //xowf/de:workflow.wf set :masterWorkflow en:Workflow.form Action select -next_state created -label #xowf.online-exam-select# Action publish -next_state published -label #xowf.online-exam-publish# Action unpublish -next_state done -label #xowf.online-exam-unpublish# Action republish -next_state published -label #xowf.online-exam-republish# Action restart -next_state initial -label #xowf.restart# State parameter { {extra_css {/resources/xowf/test-item.css}} } State initial -actions {select} -form en:select_question.form -view_method edit State created -actions {publish restart} -form_loader load_form -view_method edit \ -form "#xowf.online-exam-draft_exam#" State published -actions {unpublish} -form_loader load_form -view_method edit \ -form "#xowf.online-exam-open#" State done -actions {republish restart} -form_loader load_form -view_method edit \ -form "#xowf.online-exam-closed#" ######################################################################## # Activate action select: After the teacher has selected the # exercises, the answer workflow is created. # select proc activate {obj} { [[$obj wf_context] wf_container] create_answer_workflow $obj } ######################################################################## # Activate action publish: delete all responses for the workflow and # publish user participation link. # publish proc activate {obj} { [[$obj wf_context] wf_container] delete_all_answer_data $obj :publish_link $obj } ######################################################################## # Activate action republish: publish user participation link. # republish proc activate {obj} { :publish_link $obj } ######################################################################## # When the user un-publishes an exam, just the user participation # link should be removed for the users # unpublish proc activate {obj} { :unpublish_link $obj } ######################################################################## # create_answer_workflow: create a workflow based on the template # provided in this method for answering the question for the # students. The name of the workflow is derived from the workflow # instance and recorded in the formfield "wfName". # :proc create_answer_workflow {obj} { #:log "create_answer_workflow $obj" # first delete workflow and data, when it exists if {[$obj property wfName] ne ""} { set wf [:delete_all_answer_data $obj] if {$wf ne ""} {$wf delete} } # create a fresh workflow set wfName [$obj name].wf $obj set_property -new 1 wfName $wfName set wfMaster ${:masterWorkflow} set wfTitle [$obj property _title] set questionObjs [[[$obj wf_context] wf_container] get_questions $obj] set wfQuestionNames {} set wfQuestionTitles {} set attributeNames {} foreach form_obj $questionObjs { lappend attributeNames [xowf::test_item::renaming_form_loader \ form_name_based_attribute_stem [$form_obj name]] lappend wfQuestionNames ../[$form_obj name] lappend wfQuestionTitles [$form_obj title] } set wfID [$obj item_id] set wfDef [subst -nocommands { set wfID $wfID set wfTitle "$wfTitle" set wfQuestionNames [list $wfQuestionNames] set wfQuestionTitles [list $wfQuestionTitles] xowf::include /packages/xowf/lib/online-exam-answer.wf [list wfID wfTitle wfQuestionNames wfQuestionTitles] }] set attributeNames [join $attributeNames ,] #:log "create workflow by filling out form '$wfMaster'" set WF [::xowiki::Weblog instantiate_forms \ -parent_id [$obj parent_id] -package_id [$obj package_id] \ -default_lang [$obj lang] \ -forms $wfMaster] set f [$WF create_form_page_instance \ -name $wfName \ -nls_language [$obj nls_language] \ -publish_status ready \ -parent_id [$obj item_id] \ -package_id [$obj package_id] \ -default_variables [list title $wfTitle] \ -instance_attributes [list workflow_definition $wfDef \ form_constraints "@table:_name,_state,$attributeNames,_last_modified @cr_fields:hidden"]] $f save_new #:log "create_answer_workflow $obj DONE [$f pretty_link]" } ######################################################################## # get_answer_wf: return the workflow denoted by the property wfName in obj # :proc get_answer_wf {obj} { return [::xowiki::Weblog instantiate_forms \ -parent_id [$obj item_id] -package_id [$obj package_id] \ -default_lang [$obj lang] \ -forms [$obj property wfName]] } ######################################################################## # get_wf_instances: return the workflow instances # :proc get_wf_instances {{-initialize false} wf} { return [::xowiki::FormPage get_form_entries \ -base_item_ids [$wf item_id] -form_fields "" \ -always_queried_attributes "*" -initialize $initialize \ -publish_status all -package_id [$wf package_id]] } ######################################################################## # delete_all_answer_data: delete all instances of the answer workflow # :proc delete_all_answer_data {obj} { set wf [:get_answer_wf $obj] if {$wf ne ""} { set items [:get_wf_instances -initialize false $wf] foreach i [$items children] { $i delete } } return $wf } ######################################################################## # publish_link: make the user participation link available for the # target group # Action instproc publish_link {obj} { set aLink [$obj pretty_link -query m=answer] util_user_message -html -message "[$obj name] is available as [ns_quotehtml $aLink]" # TODO: make it happen } ######################################################################## # unpublish_link: remove the user participation link for the target # group # Action instproc unpublish_link {obj} { util_user_message -html -message "[$obj name] is closed" # TODO: make it happen } ######################################################################## # get_questions: load and initialize the interaction forms # :proc get_questions {obj} { set questions [lmap ref [$obj property question] { if {![string match "*/*" $ref]} { set ref [[$obj parent_id] name]/$ref } set ref }] set questionNames [join $questions |] set questionForms [::xowiki::Weblog instantiate_forms \ -package_id [$obj package_id] \ -default_lang [$obj lang] \ -forms $questionNames] if {[llength $questionForms] < 1} { error "unknown form $questionNames" } #:msg "questionNames '$questionNames', questionForms 'questionForms'" return $questionForms } ######################################################################## # form loader: create dynamically a form containing the disabled # questions as a preview and the survey results (the results can be # refreshed). This is a simplified version of get_question_form_object # of online-exam-answer.wf. # :proc load_form {ctx title} { set obj [$ctx object] set state [$obj property _state] set questions [:get_questions $obj] set counter 0 set fullQuestionForm "" set full_fc {} foreach q $questions { set raw_form [$q property form] set raw_fc [$q property form_constraints] set newName answer$counter regsub {@answer@} $raw_form "@$newName@" formContent set fc [lmap f $raw_fc { if {[string match "answer:*" $f]} { regsub answer $f $newName f append f ,disabled=true } set f}] append fullQuestionForm \ "

#xowf.question# [incr counter]

\n" \ $formContent lappend full_fc {*}$fc } set full_fc [lsort -unique $full_fc] #:log fullQuestionForm=$fullQuestionForm\n$full_fc # Remove wrapping forms regsub -all {]*>} $fullQuestionForm {} fullQuestionForm #:log fullQuestionForm=$fullQuestionForm set text "

$title

" set wf [:get_answer_wf $obj] if {$wf eq ""} { :msg "cannot get current workflow for [$obj name]" set lLink "." set tLink "." set aLink "." set pLink "." set menu "" } else { set wf_pretty_link [$wf pretty_link] set tLink "$wf_pretty_link?m=create-new&p.return_url=[::xo::cc url]&p.try_out_mode=1" set lLink "$wf_pretty_link?m=list" set aLink [$obj pretty_link -query m=answer] set pLink [$obj pretty_link -query m=print-answers] #util_user_message -html -message "$survey is available as $pLink" set menu "\[#xowf.refresh#,\ #xowf.online-exam-exam_instances#,\ #xowf.print#\]" } set extraAction "" switch [$obj property _state] { "created" { append extraAction "
" \ "#xowf.online-exam-try_out# " \ "#xowf.testrun#" } "published" { append extraAction "
" \ "#xowf.online-exam-can_answer# " \ "$aLink" } } append text "$menu $extraAction\n" set wfName [$obj property wfName] set report [expr {$wfName ne "" ? "{{form-stats -parent_id [$obj item_id] -form $wfName}}\n" : ""}] append report "
$menu" set f [::xowiki::Form new \ -set name en:question \ -form [subst {
$text
$fullQuestionForm
$report
text/html}] \ -text {} \ -anon_instances t \ -form_constraints $full_fc \ ] } ######################################################################## # # Object specific operations # ######################################################################## :object-specific { ######################################################################## # # Helper methods for the workflow context # ######################################################################## set ctx [:wf_context] set container [$ctx wf_container] if {$ctx ne $container} { $ctx forward load_form $container %proc $ctx } ######################################################################## # Extern callable methods ######################################################################## ######################################################################## # web-callable method "delete" # # Delete the workflow instance and all its associated data. # :proc www-delete {} { set ctx [::xowf::Context require [self]] [$ctx wf_container] delete_all_answer_data [self] next } ######################################################################## # web-callable method "print-answers" # # Print the answers in a somewhat printer friendly way. # :proc www-print-answers {} { set HTML "" set ctx [::xowf::Context require [self]] set wf [[$ctx wf_container] get_answer_wf [self]] if {$wf ne ""} { set items [[$ctx wf_container] get_wf_instances $wf] set withSignature [expr {[dict exists ${:instance_attributes} signature] ? [dict get ${:instance_attributes} signature] : 0 }] set examTitle ${:title} foreach i [$items children] { set uid [$i property _creation_user] set userName [acs_user::get_element -user_id $uid -element username] if {[$i state] ne "done"} { ns_log notice "online-exam: submission of $userName is not finished (state [$i state])" continue } set time [::xo::db::tcl_date [$i property _last_modified] tz_var] set pretty_date [clock format [clock scan $time] -format "%Y-%m-%d %T"] # # The call to "render_content" calls actually the # "summary_form" of online-exam-answer.wf when the submit # instance is in state "done". We set the __feedback_mode to # get the auto-correction included. # $i set __feedback_mode 2 set question_form [$i render_content] if {$withSignature} { set answerAttributes [xowf::test_item::renaming_form_loader \ answer_attributes [$i instance_attributes]] set sha256 [ns_md string -digest sha256 $answerAttributes] set signatureString "
Aktuelle Signatur: $sha256
\n" set submissionSignature [$i property signature ""] if {$submissionSignature ne ""} { append signatureString "
Abgabe Signatur: $submissionSignature
\n" } } else { set signatureString "" } append HTML "\n
" \ "

$examTitle - IP [$i property ip]

" \ "

$userName · [::xo::get_user_name $uid] · $pretty_date

" \ $signatureString \ $question_form \ "
\n" } } if {$HTML ne ""} { ns_return 200 text/html [subst { $HTML }] } else { util_user_message -html -message "No answer data available" ad_returnredirect [::xo::cc url] } ad_script_abort } ######################################################################## # web-callable method "answer" # # answer the exam; this is a convenience routine to shorten # the published URL; make sure that no-one tries to start the answer # workflow in a state different to "published". # :proc www-answer {} { if {[:property _state] ne "published"} { util_user_message -html -message "Cannot start answer workflow in this state" } else { set ctx [::xowf::Context require [self]] set wf [[$ctx wf_container] get_answer_wf [self]] $wf www-create-or-use -parent_id [:item_id] } } } # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: