@@ -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 "
\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 {
+ }]
+ }
+ 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