Index: openacs-4/packages/xowf/lib/inclass-exam.wf =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/inclass-exam.wf,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xowf/lib/inclass-exam.wf 21 Feb 2020 13:45:15 -0000 1.1.2.1 @@ -0,0 +1,463 @@ +# -*- Tcl -*- +######################################################################## +# Inclass-Exam workflow, designed similar to online-exam +# ======================================================== +# +# 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:TestItemText.form} +# {entry -name New.Item.ShortTextInteraction -form en:TestItemShortText.form} +# {entry -name New.Item.MCInteraction -form en:TestItemMC.form} +# {entry -name New.Item.SCInteraction -form en:TestItemSC.form} +# {entry -name New.Item.UploadInteraction -form en:TestItemUpload.form} +# {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), +# - "qrcode" (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 0 + +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.inclass-exam-draft_exam#" +State published -actions {unpublish} -form_loader load_form -view_method edit \ + -form "#xowf.inclass-exam-open#" +State done -actions {republish restart} -form_loader load_form -view_method edit \ + -form "#xowf.inclass-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/inclass-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_answers $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#\]" + } + } + + switch $state { + "created" - + "done" - + "published" { + # + # In inclass cases, never show all questions on screen, since + # the teacher might have the screen on the projector. + # + template::add_body_script -script urn:ad:js:bootstrap3 + set fullQuestionForm [subst { + +
+ $fullQuestionForm +
+ }] + } + } + + set extraAction "" + switch $state { + "created" { + append extraAction "
" \ + "#xowf.online-exam-try_out# " \ + "#xowf.testrun#" + } + "published" { + append extraAction "
" \ + "#xowf.online-exam-can_answer# " \ + "$aLink" + } + } + + set answerStatus "" + set marked "" + if {$state in {published done}} { + if {$state eq "done"} { + [$ctx object] setCSSDefaults + set marked [xowf::test_item::answer_manager marked_results -obj $obj -wf $wf $combined_form_info] + } + set answerStatus [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] + } + + set qrCode "" + set countdownHTML "" + if {$state eq "published"} { + set src [$obj pretty_link -query m=qrcode] + set qrCode [subst {
}] + set total_minutes [::xowf::test_item::question_manager total_minutes $combined_form_info] + set publish_time [::xo::db::tcl_date [$obj last_modified] tz] + set target_time [clock format [expr {[clock scan $publish_time] + $total_minutes*60}] \ + -format %Y-%m-%dT%H:%M:%S] + set countdownHTML [xowf::test_item::answer_manager countdown_timer \ + -target_time $target_time -id "countdown"] + } + + # Remove wrapping forms + regsub -all {]*>} $fullQuestionForm {} fullQuestionForm + + append text [subst { +
+
$answerStatus
+
$fullQuestionForm
+
$qrCode
+
$countdownHTML
+
+ }] + + set footer "$menu $extraAction" + + #-form [subst {
$text
$fullQuestionForm
$report
text/html}] + set f [::xowiki::Form new \ + -destroy_on_cleanup \ + -set name en:question \ + -form [subst {
$text$marked$footer
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 form_info [::xowf::test_item::question_manager combined_question_form -with_numbers [self]] + + 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 result_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_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_id ne "" && [$i item_id] ne $filter_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] + } + } + + :proc www-qrcode {} { + set aLink [:pretty_link -absolute true -query m=answer] + set fn /tmp/qr-${:item_id}.png + exec qrencode -o $fn -l h $aLink + ns_returnfile 200 image/png $fn + ad_script_abort + } + + ######################################################################## + # 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_answers $wf] + set answered [xowf::test_item::answer_manager get_answers -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: