# -*- Tcl -*- ######################################################################## # Online-Exam workflow # ==================== # # Defining exams: This workflow lets a teacher choose from a # predefined set of exam questions, which are typically open text, # short text, single or multiple choice questions. The teacher # selects test questions via drag and drop. The teacher can perform a # test run of the created exam, and can get the results via a result # table. # # Publishing and closing exams: When a 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. # # An admin might with to add the following entries to the folder to ease # creation of exercises and exams # # {clear_menu -menu New} # # {entry -name New.Item.TextInteraction -form en:edit-interaction.wf -query p.item_type=Text} # {entry -name New.Item.ShortTextInteraction -form en:edit-interaction.wf -query p.item_type=ShortText} # {entry -name New.Item.SCInteraction -form en:edit-interaction.wf -query p.item_type=SC} # {entry -name New.Item.MCInteraction -form en:edit-interaction.wf -query p.item_type=MC} # {entry -name New.Item.ReorderInteraction -form en:edit-interaction.wf -query p.item_type=Reorder} # {entry -name New.Item.UploadInteraction -form en:edit-interaction.wf -query p.item_type=Upload} # # {entry -name New.App.Exam -label "Online Exam" -form en:online-exam.wf} # # The policy has to allow the following methods on FormPages: # # - "answer" (for students), # - "edit" (for students), # - "poll" (for teachers), # - "print-answers" (for teachers), # - "print-answer-table" (for teachers), # - "delete" (for teachers), # # Gustaf Neumann, Feb 2012 ######################################################################## set :autoname 1 ;# to avoid editable name field set :policy ::xowf::test_item::test-item-policy-publish set :debug 0 set :live_updates 1 set :fc_repository { {countdown_audio_alarm:boolean,horizontal=true,default=t,label=#xowf.Countdown_audio_alarm#,help_text=#xowf.Countdown_audio_alarm_help_text#} {shuffle_items:boolean,horizontal=true,label=#xowf.randomized_items#,help_text=#xowf.randomized_items_help_text#} {max_items:number,min=1,label=#xowf.Max_items#,help_text=#xowf.Max_items_help_text#} {allow_paste:boolean,horizontal=true,default=t,label=#xowf.Allow_paste#,help_text=#xowf.Allow_paste_help_text#} {allow_spellcheck:boolean,horizontal=true,default=t,label=#xowf.Allow_spellcheck#,help_text=#xowf.Allow_spellcheck_help_text#} {allow_translation:boolean,horizontal=true,default=f,label=#xowf.Allow_translation#,help_text=#xowf.Allow_translation_help_text#} {show_minutes:boolean,horizontal=true,default=t,label=#xowf.Show_minutes#,help_text=#xowf.Show_minutes_help_text#} {show_points:boolean,horizontal=true,default=t,label=#xowf.Show_points#,help_text=#xowf.Show_points_help_text#} {show_ip:boolean,horizontal=true,default=t,label=#xowf.Show_IP#,help_text=#xowf.Show_IP_help_text#} {time_budget:range,default=100,min=100,max=300,step=5,with_output=t,form_item_wrapper_CSSclass=form-inline,output_suffix=%,label=#xowf.Time_budget#,help_text=#xowf.Time_budget_help_text#} {synchronized:boolean,horizontal=true,default=f,label=#xowf.Synchronized#,help_text=#xowf.Synchronized_help_text#} {time_window:time_span,label=#xowf.Exam_time_window#,help_text=#xowf.Exam_time_window_help_text#} {proctoring:boolean,horizontal=true,default=f,label=#xowf.Proctoring#,help_text=#xowf.Proctoring_help_text#} {proctoring_options:checkbox,horizontal=true,options={Desktop d} {Camera c} {Audio a} {Statement s},default=d c a s,label=#xowf.Proctoring_options#,help_text=#xowf.Proctoring_options_help_text#,swa?:disabled=1} {proctoring_record:boolean,horizontal=true,default=t,label=#xowf.Proctoring_record#,help_text=#xowf.Proctoring_record_help_text#} {signature:boolean,horizontal=true,default=f,label=#xowf.Signature#,help_text=#xowf.Signature_help_text#} {grading:grading_scheme,required,default=none,label=#xowf.Grading_scheme#,help_text=#xowf.Grading_scheme_help_text#} } Action select -next_state created -label #xowf.online-exam-select# \ -title #xowf.online-exam-title-select# Action publish -next_state published -label #xowf.online-exam-publish# \ -title #xowf.online-exam-title-publish# Action unpublish -next_state done -label #xowf.online-exam-unpublish# Action republish -next_state published -label #xowf.online-exam-republish# \ -title #xowf.online-exam-title-republish# Action restart -next_state initial -label #xowf.restart# \ -title #xowf.online-exam-title-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} { xowf::test_item::answer_manager create_workflow \ -answer_workflow /packages/xowf/lib/online-exam-answer.wf \ $obj } ######################################################################## # Activate action publish: delete all responses for the workflow and # publish user participation link. # publish proc activate {obj} { xowf::test_item::answer_manager 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 } ######################################################################## # 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 in the LMS } ######################################################################## # 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 in the LMS } ######################################################################## # form loader: create dynamically a form containing the disabled # questions as a preview and the survey results (the results can be # refreshed). # :proc load_form {ctx title} { set obj [$ctx object] set state [$obj property _state] set combined_form_info [::xowf::test_item::question_manager combined_question_form -with_numbers $obj] set fullQuestionForm [dict get $combined_form_info form] set full_fc [dict get $combined_form_info disabled_form_constraints] #:log fullQuestionForm=$fullQuestionForm set text "

$title

" set menu "" set wf [xowf::test_item::answer_manager get_answer_wf $obj] if {$wf eq ""} { :msg "cannot get current workflow for [$obj name]" set lLink "." set tLink "." set aLink "." set pLink "." } else { # # Always compute the testrun and answer link. # set wf_pretty_link [$wf pretty_link] set tLink [export_vars -base $wf_pretty_link { {m create-new} {p.return_url "[::xo::cc url]"} {p.try_out_mode 1} {title "[$obj title]"} }] set aLink [$obj pretty_link -query m=answer] # # If there are answers, include the full menu. # set answers [xowf::test_item::answer_manager get_answer_attributes $wf] if {[llength $answers] > 0} { set lLink "$wf_pretty_link?m=list" set pLink1 [$obj pretty_link -query m=print-answers] set pLink2 [$obj pretty_link -query m=print-answer-table] set menu "\[" if {[acs_user::site_wide_admin_p -user_id [::xo::cc user_id]]} { append menu "#xowf.online-exam-exam_instances#, " } append menu \ "#xowf.online-exam-protocol#, " \ "#xowf.online-exam-results-table#\]" } } set extraAction "" switch $state { "created" { append extraAction "
" \ "#xowf.online-exam-try_out# " \ "#xowf.testrun#" } "published" { append extraAction "
" \ "#xowf.online-exam-can_answer# " \ "$aLink" } } if {$state in {published done}} { if {$state eq "done"} { set marked [xowf::test_item::answer_manager marked_results -obj $obj -wf $wf $combined_form_info] } set answerStats [xowf::test_item::answer_manager answers_panel \ -heading "#xowf.online-exam-submitted_exams_heading#" \ -submission_msg "#xowf.online-exam-submitted_exams_msg#" \ -polling=[expr {${:live_updates} && $state ni {initial created done}}] \ -manager_obj $obj \ -target_state done \ -wf $wf] } else { set answerStats "" } append text "$answerStats\n" append report "$menu $extraAction" # Remove wrapping forms regsub -all {]*>} $fullQuestionForm {} fullQuestionForm set f [::xowiki::Form new \ -destroy_on_cleanup \ -set name en:question \ -form [subst {
$text
$fullQuestionForm
$report
text/html}] \ -text {} \ -anon_instances t \ -form_constraints $full_fc \ ] } ######################################################################## # # Object specific operations # ######################################################################## :object-specific { set ctx [:wf_context] set container [$ctx wf_container] if {$ctx ne $container} { $ctx forward load_form $container %proc $ctx } ${container}::Property return_url -default "" -allow_query_parameter true # # Unset the actual query return_url, since we want to use it via # property. In some cases, we have to set it explicitly from the # property, e.g. in www-delete. # ::xo::cc unset_query_parameter return_url ######################################################################## # web-callable method "delete" # # Delete the workflow instance and all its associated data. # :proc www-delete {} { ::xo::cc set_query_parameter return_url [:property return_url] xowf::test_item::answer_manager delete_all_answer_data [self] next } ######################################################################## # web-callable method "print-answer-table" # # Print the answers in a somewhat printer friendly way. # :proc www-print-answer-table {} { set HTML "" set ctx [::xowf::Context require [self]] set wf [xowf::test_item::answer_manager get_answer_wf [self]] if {$wf ne ""} { set items [xowf::test_item::answer_manager get_wf_instances $wf] set items2 [$items deep_copy] foreach i [$items2 children] { $i set online-exam-userName [acs_user::get_element -user_id [$i creation_user] -element username] $i set online-exam-fullName [::xo::get_user_name [$i creation_user]] } set HTML [::xowf::test_item::answer_manager results_table \ -package_id ${:package_id} \ -items $items2 \ [self]] $items2 destroy } if {$HTML eq ""} { set HTML "#xowiki.no_data#" } else { set HTML "

#xowf.online-exam-results-table#

$HTML" } set return_url [[$wf package_id] query_parameter local_return_url:localurl [:pretty_link]] append HTML "

#xowiki.back#

\n" xo::Page requireCSS /resources/xowf/test-item.css :www-view $HTML } ######################################################################## # 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 [xowf::test_item::answer_manager get_answer_wf [self]] if {$wf ne ""} { set items [xowf::test_item::answer_manager get_wf_instances $wf] set withSignature [expr {[dict exists ${:instance_attributes} signature] ? [dict get ${:instance_attributes} signature] : 0 }] set examTitle ${:title} set filter_submission_id [[$wf package_id] query_parameter id:integer ""] foreach i [$items children] { $i set online-exam-userName [acs_user::get_element -user_id [$i creation_user] -element username] $i set online-exam-fullName [::xo::get_user_name [$i creation_user]] } $items orderby online-exam-userName foreach i [$items children] { set userName [$i set online-exam-userName] set fullName [$i set online-exam-fullName] if {[$i state] ne "done"} { ns_log notice "online-exam: submission of $userName is not finished (state [$i state])" continue } if {$filter_submission_id ne "" && [$i item_id] ne $filter_submission_id} { continue } # # 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 "
online-exam-actual_signature: $sha256
\n" set submissionSignature [$i property signature ""] if {$submissionSignature ne ""} { append signatureString "
#xowf.online-exam-submission_signature#: $submissionSignature
\n" } } else { set signatureString "" } 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"] append HTML "\n
" \ "

$userName · $fullName · $pretty_date · IP [$i property ip]

" \ $signatureString \ $question_form \ "
\n" } } if {$HTML eq ""} { set HTML "#xowiki.no_data#" } else { set HTML "

#xowf.online-exam-protocol#

$HTML" } set return_url [[$wf package_id] query_parameter local_return_url:localurl [:pretty_link]] append HTML "

#xowiki.back#

\n" ::xo::cc set_parameter template_file view-plain-master ::xo::cc set_parameter MenuBar 0 xo::Page requireCSS /resources/xowf/test-item.css :www-view $HTML } ######################################################################## # web-callable method "answer" # # Create or use an answering workflow for the current exam. This is # a convenience routine to shorten the published URL. # :proc www-answer {} { # # Make sure that no-one tries to start the answer workflow in a # state different to "published". # if {[:property _state] ne "published"} { util_user_message -html -message "Cannot start answer workflow in this state" } else { set wf [xowf::test_item::answer_manager get_answer_wf [self]] $wf www-create-or-use -parent_id [:item_id] } } ######################################################################## # AJAX call "poll" # # Return statistics about working and finished exams. # :proc www-poll {} { set wf [xowf::test_item::answer_manager get_answer_wf [self]] set answers [xowf::test_item::answer_manager get_answer_attributes $wf] set answered [xowf::test_item::answer_manager get_answer_attributes -state done $wf] ns_return 200 text/plain [llength $answered]/[llength $answers] #ns_log notice "MASTER POLL [self] ${:name}, returned [llength $answered]/[llength $answers]" ad_script_abort } } # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: