Index: openacs-4/packages/xowf/lib/inclass-exam.wf =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/inclass-exam.wf,v diff -u -r1.1.2.15 -r1.1.2.16 --- openacs-4/packages/xowf/lib/inclass-exam.wf 4 Apr 2020 11:32:34 -0000 1.1.2.15 +++ openacs-4/packages/xowf/lib/inclass-exam.wf 17 Apr 2020 21:33:04 -0000 1.1.2.16 @@ -204,7 +204,7 @@ # In inclass cases, never show all questions on screen, since # the teacher might have the screen on the projector. # - template::add_body_script -src urn:ad:js:bootstrap3 + template::add_script -src urn:ad:js:bootstrap3 set fullQuestionForm [subst {
@@ -317,7 +317,7 @@ ######################################################################## # web-callable method "print-answer-table" # - # Print the answers in a somewhat printer friendly way. + # Print the answers in a tabular form. # :proc www-print-answer-table {} { set HTML "" @@ -352,6 +352,17 @@ } ######################################################################## + # web-callable method "view-my-exam " + # + # Provide feedback to the student about the results. + # + :proc www-view-my-exam {} { + ::xo::cc set_query_parameter creation_user [ad_conn user_id] + ::xo::cc set_query_parameter as_student 1 + :www-print-answers + } + + ######################################################################## # web-callable method "print-answers" # # Print the answers in a somewhat printer friendly way. @@ -361,13 +372,20 @@ 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 package_id [$wf package_id] + set filter_id [$package_id query_parameter id:integer ""] + set creation_user [$package_id query_parameter creation_user:integer ""] + set revision_id [$package_id query_parameter rid:integer ""] + set as_student [$package_id query_parameter as_student:boolean 0] + + set items [xowf::test_item::answer_manager get_wf_instances \ + {*}[expr {$creation_user ne "" ? "-creation_user $creation_user" : ""}] \ + {*}[expr {$filter_id ne "" ? "-item_id $filter_id" : ""}] \ + $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 ""] - set revision_id [[$wf package_id] query_parameter rid:integer ""] if {$revision_id ne ""} { set r [::xowiki::FormPage get_instance_from_db -revision_id $revision_id] @@ -394,9 +412,7 @@ ns_log notice "online-exam: submission of $userName is not finished (state $state)" #continue } - if {$filter_id ne "" && [$i item_id] ne $filter_id} { - continue - } + set revisions [$i get_revision_sets] if {[llength $revisions] <=1 } { # just an initial revision @@ -406,18 +422,25 @@ # # The call to "render_content" calls actually the - # "summary_form" of online-exam-answer.wf when the submit + # "summary_form" of online/inclass-exam-answer.wf when the submit # instance is in state "done". We set the __feedback_mode to # get the auto-correction included. # + set achieved_points {} xo::cc eval_as_user -user_id [$i creation_user] { $i set __feedback_mode 2 set question_form [$i render_content] + if {$withSignature || $as_student} { + set answerAttributes [xowf::test_item::renaming_form_loader \ + answer_attributes [$i instance_attributes]] + if {$as_student} { + set achieved_points [xowf::test_item::answer_manager achieved_points \ + -answer_object $i -answer_attributes $answerAttributes] + } + } } 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 ""] @@ -430,11 +453,78 @@ set time [::xo::db::tcl_date [$i property _last_modified] tz_var] set pretty_date [clock format [clock scan $time] -format "%Y-%m-%d"] + + if {$filter_id ne "" && [:property proctoring] eq "t"} { + set user_id [$i creation_user] + set img_url [:pretty_link -query m=proctor-image&user_id=$user_id] + + set proctoring_dir [acs_root_dir]/proctoring/${:item_id}/$user_id/ + set files [glob -nocomplain -path $proctoring_dir *.*] + ns_log notice "proctoring_dir $proctoring_dir files $files" + + set start_date [ns_set get [lindex $revisions 0] creation_date] + set end_date [ns_set get [lindex $revisions end] creation_date] + set start_clock [clock scan [::xo::db::tcl_date $start_date tz_var]] + set end_clock [clock scan [::xo::db::tcl_date $end_date tz_var]] + foreach r $revisions { + ns_log notice "$r: [ns_set get $r revision_id] creation date [ns_set get $r creation_date]" + } + + ns_log notice "start date $start_date end_date $end_date / $start_clock $end_clock" + foreach f $files { + ns_log notice "check: $f" + if {[regexp {/([^/]+)-(\d+)[.](webm|png|jpeg)$} $f . type stamp ext]} { + set inWindow [expr {$stamp >= $start_clock && $stamp <= $end_clock}] + ns_log notice "parsed $type $stamp $ext $inWindow $stamp \ + [clock format $stamp -format {%m-%d %H:%M:%S}] >= \ + $start_clock ([expr {$stamp >= $start_clock}]) \ + && $stamp <= $end_clock ([expr {$stamp <= $end_clock}])" + if {$inWindow} { + dict set image $stamp $type $stamp + } + } + } + set markup "" + foreach ts [lsort -integer [dict keys $image]] { + ns_log notice "ts $ts [dict get $image $ts]" + append markup [subst {
[clock format $ts -format {%Y-%m-%d %H:%M:%S}]
}] + append markup {
} + foreach type {camera-image desktop-image} { + if {[dict exists $image $ts $type]} { + append markup [subst {}] + } + } + if {[dict exists $image $ts camera-audio]} { + append markup [subst {
\n + } + + + set question_form [subst { +
+
+
$question_form
+
$markup
+
+
+ }] + } + set view [expr {$as_student + ? "student" + : $filter_id ne "" + ? "revision_overview" + : "default"}] + set runtime_panel [xowf::test_item::answer_manager runtime_panel \ + -revision_id $revision_id \ + -view $view \ + -achieved_points $achieved_points \ + $i] append HTML [subst {

$userName · $fullName · $pretty_date

- [xowf::test_item::answer_manager runtime_panel -revision_id $revision_id -filter_id $filter_id $i] + $runtime_panel
$signatureString $question_form @@ -448,7 +538,7 @@ } else { set HTML "

#xowf.online-exam-protocol#

$HTML" } - set return_url [[$wf package_id] query_parameter local_return_url:localurl [:pretty_link]] + set return_url [$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 @@ -514,11 +604,13 @@ ::xo::cc set_parameter MenuBar 0 if {[file exists [acs_package_root_dir xowf]/lib/proctored-page.adp]} { - set object_id ${:object_id} + set object_id ${:item_id} set object_url $link + set upload_url /xowf-proctoring-upload ${:package_id} return_page -adp /packages/xowf/lib/proctored-page -variables { object_id object_url + upload_url } } else { return [:www-view [subst { @@ -528,6 +620,18 @@ }]] } } + :proc www-proctor-image {} { + set type [${:package_id} query_parameter type:ascii ""] + set ts [${:package_id} query_parameter ts:integer ""] + set ext [${:package_id} query_parameter e:wordchar ""] + set user_id [${:package_id} query_parameter user_id:integer ""] + set id ${:item_id} + set proctoring_dir [acs_root_dir]/proctoring/$id/$user_id + set png_path $proctoring_dir/$type-$ts.$ext + #ns_log notice "image: $png_path" + ns_returnfile 200 image/png $png_path + ad_script_abort + } ######################################################################## # AJAX call "poll" Index: openacs-4/packages/xowf/tcl/test-item-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/test-item-procs.tcl,v diff -u -r1.7.2.45 -r1.7.2.46 --- openacs-4/packages/xowf/tcl/test-item-procs.tcl 10 Apr 2020 12:09:23 -0000 1.7.2.45 +++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 17 Apr 2020 21:33:04 -0000 1.7.2.46 @@ -1247,18 +1247,27 @@ :public method get_wf_instances { {-initialize false} {-orderby ""} + -creation_user:integer + -item_id:integer wf:object } { # get_wf_instances: return the workflow instances :assert_assessment_container $wf + set extra_where_clause "" + foreach var {creation_user item_id} { + if {[info exists $var]} { + append extra_where_clause "AND $var = [set var] " + } + } return [::xowiki::FormPage get_form_entries \ -base_item_ids [$wf item_id] \ -form_fields "" \ -always_queried_attributes "*" \ -initialize $initialize \ -orderby $orderby \ + -extra_where_clause $extra_where_clause \ -publish_status all \ -package_id [$wf package_id]] } @@ -1347,16 +1356,43 @@ return $result } + ######################################################################## + :public method achieved_points {-answer_object:object -answer_attributes:required } { + # + # This method has to be called after the instance was rendered, + # since it uses the produced form_fields. + # + set all_form_fields [::xowiki::formfield::FormField info instances -closure] + set totalPoints 0 + set totalPossiblePoints 0 + foreach a [dict keys $answer_attributes] { + set f [$answer_object lookup_form_field -name $a $all_form_fields] + if {[$f exists correction_data]} { + set cd [$f set correction_data] + #ns_log notice "FOO: $a <$f> $cd" + if {[dict exists $cd points]} { + set totalPoints [expr {$totalPoints + [dict get $cd points]}] + set totalPossiblePoints [expr {$totalPossiblePoints + [$f set test_item_minutes]}] + } else { + ns_log notice "$a: no points in correction_data, ignoring in points calculation" + } + } + } + return [list achievedPoints $totalPoints possiblePoints $totalPossiblePoints] + } + + ######################################################################## :public method runtime_panel { {-revision_id ""} - {-filter_id ""} + {-view default} + {-achieved_points ""} answerObj:object } { # # Return statistics for the provided object: - # - minimal statistics: when 'filter_id' is empty - # - statistics with clickable revisions: when 'filter_id' is non empty - # - per-revision statistics: when revision_id is provided + # - minimal statistics: when view default + # - statistics with clickable revisions: when view = revision_overview + # - per-revision statistics: when view = revision_overview and revision_id is provided # set revision_sets [$answerObj get_revision_sets] set item_id [$answerObj item_id] @@ -1366,7 +1402,12 @@ set current_question [expr {[dict get [$answerObj instance_attributes] position] + 1}] set page_info "#xowf.question#: $current_question" - if {$filter_id ne ""} { + if {$view eq "default"} { + set url [ad_return_url]&id=$item_id + set revisionDetails "#xowf.nr_changes#: [llength $revision_sets]
" + } elseif {$view eq "student"} { + set revisionDetails "" + } elseif {$view eq "revision_overview"} { set displayed_revision_info "" set live_revision_info "" set make_live_info "" @@ -1412,38 +1453,55 @@
$displayed_revision_info
$live_revision_info
$make_live_info
+
}] - } else { - set url [ad_return_url]&id=$item_id - set revisionDetails "#xowf.nr_changes#: [llength $revision_sets]" } if {$revision_id eq ""} { set revision_sets [:revisions_up_to $revision_sets $live_revision_id] } set last_published [:last_time_in_state -obj [$answerObj parent_id] -state published] set duration [:get_duration -exam_published_time $last_published $revision_sets] - set IPs [:get_IPs $revision_sets] set state [$answerObj state] if {$state eq "done"} { set submission_info "#xowf.submitted#" } else { set submission_info "#xowf.not_submitted# ($page_info)" } + if {[dict exists $duration examPublished]} { set publishedInfo "#xowf.Exam_published#: [dict get $duration examPublished]
" set extraDurationInfo " - #xowf.since_published#: [dict get $duration examPublishedDuration]" } else { set publishedInfo "" set extraDurationInfo "" } + if {$view eq "student"} { + set IPinfo "" + set statusInfo "" + set extraDurationInfo "" + } else { + set IPinfo [subst {IP: [:get_IPs $revision_sets]}] + set statusInfo "#xowf.Status#: $submission_info
" + } + if {$achieved_points ne ""} { + set possiblePoints [format %.2f [dict get $achieved_points possiblePoints]] + set achievedPoints [format %.2f [dict get $achieved_points achievedPoints]] + set percentage [format %.2f [expr {$achievedPoints*100.0/$possiblePoints}]] + set achievedPointsInfo [subst { + Punkte: $achievedPoints von möglichen $possiblePoints Punkten, $percentage% + }] + } else { + set achievedPointsInfo "" + } set HTML [subst { $publishedInfo - $revisionDetails
- #xowf.Status#: $submission_info
+ $revisionDetails + $statusInfo #xowf.Duration#: [dict get $duration from] - [dict get $duration to] ([dict get $duration duration]$extraDurationInfo)
- IP: $IPs + $IPinfo + $achievedPointsInfo }] return $HTML } @@ -1622,6 +1680,7 @@ #ns_log notice "[$p creation_user] [$ff_obj name] [$p property $property] -> [$ff_obj set evaluated_answer_result]" if {[$ff_obj exists grading_score]} { set r [$ff_obj set grading_score] + #ns_log notice "==== [$ff_obj name] grading_score => $r" } else { set r [expr {[$ff_obj set evaluated_answer_result] eq "correct" ? 100.0 : 0.0}]* #ns_log notice [$ff_obj serialize] @@ -1921,11 +1980,16 @@ return $result } - :method add_in_postion_to_fc {-fc -position} { + :method add_to_fc {-fc:required -position -minutes} { return [lmap c $fc { if {[regexp {^[^:]+_:} $c]} { - append c ,in_position=$position - ns_log notice "APPEND $c" + if {[info exists position]} { + append c ,test_item_in_position=$position + } + if {[info exists minutes]} { + append c ,test_item_minutes=$minutes + } + #ns_log notice "APPEND $c" } set c }] @@ -1963,11 +2027,13 @@ title $form_title \ minutes $minutes \ number $number] - lappend full_fc [:add_in_postion_to_fc \ + lappend full_fc [:add_to_fc \ -fc [$form_obj property form_constraints] \ + -minutes $minutes \ -position $position] - lappend full_disabled_fc [:add_in_postion_to_fc \ + lappend full_disabled_fc [:add_to_fc \ -fc [$form_obj property disabled_form_constraints] \ + -minutes $minutes \ -position $position] incr position } @@ -2154,12 +2220,13 @@ ::xowiki::policy1 copy ::xowf::test_item::test-item-policy-answer # - # Add policy rules as used in two demo workflow. We are permissive + # Add policy rules as used in two demo workflows. We are permissive # for student actions and require admin right for teacher activities. # test-item-policy-publish contains { Class create FormPage -array set require_permission { answer {{item_id read}} + view-my-exam {{item_id read}} proctor-answer {{item_id read}} proctor {{item_id read}} poll admin