Index: openacs-4/packages/xowf/xowf.info
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/xowf.info,v
diff -u -N -r1.12.2.56 -r1.12.2.57
--- openacs-4/packages/xowf/xowf.info 5 Nov 2021 08:09:18 -0000 1.12.2.56
+++ openacs-4/packages/xowf/xowf.info 15 Nov 2021 17:02:37 -0000 1.12.2.57
@@ -10,16 +10,16 @@
t
xowf
-
+
Gustaf Neumann
XoWiki Content Flow - an XoWiki based workflow system implementing state-based behavior of wiki pages and forms
2021-09-15
WU Vienna
BSD-Style
2
-
-
+
+
Index: openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml,v
diff -u -N -r1.2.2.56 -r1.2.2.57
--- openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 5 Nov 2021 08:09:18 -0000 1.2.2.56
+++ openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 15 Nov 2021 17:02:37 -0000 1.2.2.57
@@ -224,4 +224,13 @@
2-Spaltig
Automatische Speicherung nicht m�glich, da die Bearbeitungszeit abgelaufen ist. Speichern Sie das Ergebnis und geben sie ab!
+
+ Pool-Frage
+ Pool-Frage
+ Fragenpool
+ Namesmuster f�r Pool-Fragen
+ Fragentypen
+ Verf�gbare Pool-Fragen
+ Pool-Fragen nach Typ
+
Index: openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml,v
diff -u -N -r1.2.2.61 -r1.2.2.62
--- openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 5 Nov 2021 08:09:18 -0000 1.2.2.61
+++ openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 15 Nov 2021 17:02:37 -0000 1.2.2.62
@@ -252,4 +252,12 @@
Subquestion with points
Subquestion with title
Autosave operation rejected since submmission is overdue. Please save your answer and submit now.
+
+ Pool Question
+ Pool Question
+ Pool Folder
+ Name Pattern
+ Item Types
+ Available Pool Items
+ Pool Item Statistics
Index: openacs-4/packages/xowf/lib/answer-single-question.wf
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/answer-single-question.wf,v
diff -u -N -r1.1.2.4 -r1.1.2.5
--- openacs-4/packages/xowf/lib/answer-single-question.wf 30 May 2021 18:59:59 -0000 1.1.2.4
+++ openacs-4/packages/xowf/lib/answer-single-question.wf 15 Nov 2021 17:02:37 -0000 1.1.2.5
@@ -85,18 +85,26 @@
set parent_obj [::xo::db::CrClass get_instance_from_db -item_id $parent_id]
#
- # In case shuffling is required, fetch via the shuffled position.
- #
- set shuffle_id [expr {[$parent_obj property shuffle_items 0] ? [$obj creation_user] : -1}]
-
- #
# Load the form. This is here simply the parent object
#
- set form_obj ::[$obj parent_id]
+ set form_obj $parent_obj
- foreach chunk [::xowf::test_item::question_manager describe_form -asHTML $form_obj] {
+ foreach chunk [::xowf::test_item::question_manager describe_form \
+ -field_name answer -asHTML $form_obj] {
util_user_message -html -message $chunk
}
+
+ if {[$form_obj property item_type] eq "PoolQuestion"} {
+ #
+ # In the case of a PoolQuestion, we have to replace the question.
+ #
+ set form_obj [::xowf::test_item::question_manager replace_pool_question \
+ -position 1 \
+ -seed [clock seconds] \
+ -field_name answer \
+ -pool_question_obj $form_obj \
+ -exam_question_names ""]
+ }
return $form_obj
}
Index: openacs-4/packages/xowf/lib/inclass-exam-answer.wf
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/inclass-exam-answer.wf,v
diff -u -N -r1.1.2.47 -r1.1.2.48
--- openacs-4/packages/xowf/lib/inclass-exam-answer.wf 5 Nov 2021 08:09:19 -0000 1.1.2.47
+++ openacs-4/packages/xowf/lib/inclass-exam-answer.wf 15 Nov 2021 17:02:37 -0000 1.1.2.48
@@ -73,7 +73,7 @@
}
Action instproc activate {obj} {
- ns_log notice "... activate [self] $obj"
+ #ns_log notice "... activate [self] $obj"
set ctx [:wf_context]
set exam_info [[$ctx wf_container] exam_info $obj]
@@ -228,25 +228,21 @@
}
set item_nr [:current_position $obj]
- #:msg "working_form_loader item_nr $item_nr [$obj instance_attributes]"
+ #ns_log notice "[self] current position => $item_nr"
set parent_id [$obj parent_id]
set parent_obj [::xo::db::CrClass get_instance_from_db -item_id $parent_id]
#
# In case shuffling is required, fetch via the shuffled position.
#
- #:msg "============ working_form_loader load form on pos $position"
-
set shuffle_id [expr {[$parent_obj property shuffle_items 0] ? [$obj creation_user] : -1}]
set position [${:QM} shuffled_index -shuffle_id $shuffle_id $parent_obj $item_nr]
- #ns_log notice "============ working_form_loader: position based on item_nr $item_nr and shuffle $shuffle_id -> $position"
#
# Load the form.
#
set form_obj [${:QM} nth_question_obj $parent_obj $position]
- #ns_log notice "load form => $form_obj (position $position [$form_obj name])"
#
# Substitute markup in the constant part of the form in the context
@@ -284,7 +280,7 @@
-for_question \
-with_minutes
- ns_log notice "============ working_form_loader: set title -position $position -item_nr $item_nr "
+ #ns_log notice "============ working_form_loader: set title -position $position -item_nr $item_nr "
#
# Disallow spellcheck/paste if required
@@ -507,15 +503,31 @@
########################################################################
:object-specific {
+ set isAnswerInstance [expr {[:is_wf_instance] == 1 && [:is_wf] == 0}]
+ #ns_log notice "==== object-specific inclass-exam-answer [self] isAnswerInstance $isAnswerInstance"
+
+ if {!$isAnswerInstance} {
+ #
+ # This happens during create-new.
+ #
+ #ns_log notice "==== object-specific inclass-exam-answer [self] not called on answerInstance"
+ return
+ }
+
#
# Ensure default value is updated for each instance individually.
#
set ctx [:wf_context]
set container [$ctx wf_container]
${container}::Property ip -default [expr {[ns_conn isconnected] ? [ad_conn peeraddr] : "nowhere"}]
+
set :QM [$container set QM]
+ ${:QM} initialize -wfi [self]
+ #ns_log notice "==== object-specific inclass-exam-answer [self] QM initialized with [self]"
- #:log "inclass-exam-answer state ${:state}"
+ set parent_obj [::xo::db::CrClass get_instance_from_db -item_id ${:parent_id}]
+
+ :log "inclass-exam-answer state ${:state}"
set ctx [:wf_context]
set container [$ctx wf_container]
if {$ctx ne $container} {
@@ -533,7 +545,20 @@
-obj [self] \
-seed ${:creation_user} \
-number $question_count
+ #
+ # After creating the seeds, replace pool questions in case these
+ # are contained. The list of pool questions will be kept per
+ # fill-out instance.
+ #
+
+ #ns_log notice "==== object-specific inclass-exam-answer [self] replace_pool_questions"
+ ${:QM} replace_pool_questions \
+ -answer_obj [self] \
+ -exam_obj $parent_obj
+ #ns_log notice "==== object-specific inclass-exam-answer [self] replace_pool_questions DONE"
+
}
+
#
# Use the current_position in the sense of the nth question of the
# user, which is not necessarily the nth question in the list of
@@ -575,29 +600,41 @@
:proc www-autosave-attribute {} {
#
- # Reject autosave in case the exam was closed already.
+ # In try-out-mode (testrun), autosave is always allowed.
#
- set exam_info [[[:wf_context] wf_container] exam_info [self]]
- set autosaveAllowed [dict get $exam_info open]
- if {$autosaveAllowed} {
- set parent_obj [::xo::db::CrClass get_instance_from_db -item_id ${:parent_id}]
- set base_time [${:QM} exam_base_time -manager $parent_obj -answer_obj [self]]
- set base_clock [clock scan [::xo::db::tcl_date $base_time tz secfrac]]
-
- set seconds_working [expr {[clock seconds] - $base_clock}]
- set total_minutes [${:QM} total_minutes_for_exam -manager $parent_obj]
- set timeLeft [expr {$total_minutes*60 - $seconds_working}]
+ if {[:property try_out_mode 0]} {
+ set autosaveAllowed 1
+ } else {
#
- # The autosave operation has a 10 secs delay. To allow save operations
- # up to the last second, we accept an 10
- # secs overdue on autosave.
+ # Reject autosave in case the exam was closed already.
#
- if {$timeLeft < -10} {
- set autosaveAllowed 0
- set reason "time used up (time left $timeLeft seconds)"
+ set exam_info [[[:wf_context] wf_container] exam_info [self]]
+ set autosaveAllowed [dict get $exam_info open]
+ if {$autosaveAllowed} {
+ #
+ # Don't allow the autosave opertions, when
+ # submission is overdue.
+ #
+ set parent_obj [::xo::db::CrClass get_instance_from_db -item_id ${:parent_id}]
+ set base_time [${:QM} exam_base_time -manager $parent_obj -answer_obj [self]]
+ set base_clock [clock scan [::xo::db::tcl_date $base_time tz secfrac]]
+
+ set seconds_working [expr {[clock seconds] - $base_clock}]
+ set total_minutes [${:QM} total_minutes_for_exam -manager $parent_obj]
+ set timeLeft [expr {$total_minutes*60 - $seconds_working}]
+
+ #
+ # The autosave operation has a 10 secs delay. To allow save operations
+ # up to the last second, we accept an 10
+ # secs overdue on autosave.
+ #
+ if {$timeLeft < -10} {
+ set autosaveAllowed 0
+ set reason "time used up (time left $timeLeft seconds)"
+ }
+ } else {
+ set reason "exam closed"
}
- } else {
- set reason "exam closed"
}
if {$autosaveAllowed} {
next
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 -r1.1.2.81 -r1.1.2.82
--- openacs-4/packages/xowf/lib/inclass-exam.wf 4 Oct 2021 08:34:40 -0000 1.1.2.81
+++ openacs-4/packages/xowf/lib/inclass-exam.wf 15 Nov 2021 17:02:37 -0000 1.1.2.82
@@ -359,7 +359,8 @@
}
${container}::Property return_url -default "" -allow_query_parameter true
-
+ #ns_log notice "==== object-specific inclass-exam [self] state ${:state}"
+
if {${:state} eq "done"} {
set done_actions republish
set combined_form_info [::xowf::test_item::question_manager combined_question_form [self]]
@@ -385,8 +386,15 @@
# Check, if randomization is OK. If not, remove the "publish"
# button from the workflow.
#
+ # Note: this initialization code is always called when the
+ # workflow is initialized, which might not be wanted, when this
+ # happen during e.g. a test-run of an instance. so, maybe put this
+ # to some "render" method?
+ #
+ #ns_log notice "==== check for randomization"
set combined_form_info [::xowf::test_item::question_manager combined_question_form [self]]
set randomizationOk [dict get $combined_form_info randomization_for_exam]
+ #ns_log notice "==== check for randomization DONE"
${container}::${:state} actions \
[expr {$randomizationOk ? {publish restart} : {restart}}]
}
@@ -756,6 +764,8 @@
ns_return 200 text/plain ok
ad_script_abort
}
+
+ #ns_log notice "==== object-specific inclass-exam [self] state ${:state} DONE"
}
#
Index: openacs-4/packages/xowf/resources/prototypes/TestItemPoolQuestion.form.page
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/resources/prototypes/Attic/TestItemPoolQuestion.form.page,v
diff -u -N
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/xowf/resources/prototypes/TestItemPoolQuestion.form.page 15 Nov 2021 17:02:38 -0000 1.1.2.1
@@ -0,0 +1,11 @@
+# -*- tcl-*-
+::xowiki::Form new \
+ -name en:TestItemPoolQuestion.form \
+ -title "PoolQuestionItem" \
+ -anon_instances f \
+ -text {} \
+ -form {{} text/html} \
+ -form_constraints {
+ question:test_item,question_type=pool,feedback_level=single,label=#xowf.pool_question#
+ _name:test_item_name _description:omit _page_order:omit
+ }
Index: openacs-4/packages/xowf/resources/prototypes/select_question.form.page
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/resources/prototypes/Attic/select_question.form.page,v
diff -u -N -r1.1.2.4 -r1.1.2.5
--- openacs-4/packages/xowf/resources/prototypes/select_question.form.page 1 Nov 2021 21:19:50 -0000 1.1.2.4
+++ openacs-4/packages/xowf/resources/prototypes/select_question.form.page 15 Nov 2021 17:02:38 -0000 1.1.2.5
@@ -23,7 +23,7 @@
-form_constraints {
@cr_fields:hidden
{_title:text,label=#xowf.online-exam-name#,default=#xowf.online-exam-default_name#}
- {question:form_page,multiple=true,keep_order=true,form=en:edit-interaction.wf|en:TestItemText.form|en:TestItemShortText.form|en:TestItemMC.form|en:TestItemSC.form|en:TestItemUpload.form|en:TestItemReorder.form,required,help_text=#xowf.select_question_help_text#,label=#xowiki.questions#}
+ {question:form_page,multiple=true,keep_order=true,parent_id=.,form=en:edit-interaction.wf|en:TestItemText.form|en:TestItemShortText.form|en:TestItemMC.form|en:TestItemSC.form|en:TestItemUpload.form|en:TestItemReorder.form,required,help_text=#xowf.select_question_help_text#,label=#xowiki.questions#}
{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#}
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 -N -r1.7.2.170 -r1.7.2.171
--- openacs-4/packages/xowf/tcl/test-item-procs.tcl 4 Nov 2021 17:14:00 -0000 1.7.2.170
+++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 15 Nov 2021 17:02:38 -0000 1.7.2.171
@@ -115,7 +115,6 @@
}
TestItemField instproc correct_when_widget {{-nr 10}} {
- set dict ""
dict set dict repeat 1..10
dict set dict repeat_add_label #xowiki.form-repeatable-add-condition#
dict set dict help_text #xowiki.formfield-comp_correct_when-help_text#
@@ -300,9 +299,16 @@
set auto_correct false
set can_shuffle false
}
+ pool {
+ set interaction_class pool_question
+ set options ""
+ set auto_correct false
+ set can_shuffle false
+ }
default {error "unknown question type: ${:question_type}"}
}
- :log test_item-auto_correct=$auto_correct
+ #:log test_item-auto_correct=$auto_correct
+
#
# Handle feedback_level.
#
@@ -331,9 +337,14 @@
}
if {$can_shuffle} {
- set shuffle_options "{#xowf.shuffle_none# none} {#xowf.shuffle_peruser# peruser} {#xowf.shuffle_always# always}"
+ set shuffle_dict horizontal true
+ set shuffle_dict form_item_wrapper_CSSclass form-inline
+ set shuffle_dict default none
+ set shuffle_dict label #xowf.Shuffle#
+ set shuffle_dict options \
+ "{#xowf.shuffle_none# none} {#xowf.shuffle_peruser# peruser} {#xowf.shuffle_always# always}"
set shuffleSpec [subst {
- {shuffle {radio,horizontal=true,form_item_wrapper_CSSclass=form-inline,options=$shuffle_options,default=none,label=#xowf.Shuffle#}}
+ [list [list shuffle [:dict_to_fc -type radio $shuffle_dict]]]
{show_max {number,form_item_wrapper_CSSclass=form-inline,min=1,label=#xowf.show_max#}}
}]
} else {
@@ -380,13 +391,15 @@
{points number,form_item_wrapper_CSSclass=form-inline,min=0.0,step=0.1,label=#xowf.Points#}
}
}
-
- :create_components [subst {
+ if {${:question_type} eq "pool"} {
+ set twocolDict ""
+ }
+ :create_components [subst {
$pointsSpec
$shuffleSpec
$gradingSpec
$typeSpecificComponentSpec
- [:makeSpec -name twocol $twocolDict]
+ [expr {$twocolDict ne "" ? [:makeSpec -name twocol $twocolDict] : ""}]
{interaction {$interaction_class,$options,feedback_level=${:feedback_level},auto_correct=${:auto_correct},label=}}
[:feed_back_definition]
}]
@@ -407,7 +420,9 @@
{nr_choices 5}
{multiple true}
}
- mc_interaction set auto_correct true
+ mc_interaction set closed_question_type true
+ #mc_interaction set item_type MC ;# just used for reverse lookup in pool questions,
+ # where the old MC questions are not supported
mc_interaction instproc set_compound_value {value} {
set r [next]
@@ -442,7 +457,7 @@
# create component structure
#
set widget [test_item set richtextWidget]
- :create_components [subst {
+ :create_components [subst {
{text {$widget,required,height=150px,label=#xowf.exercise-text#}}
{mc {mc_choice,feedback_level=${:feedback_level},label=#xowf.alternative#,multiple=${:multiple},repeat=1..${:nr_choices}}}
}]
@@ -538,7 +553,7 @@
${:object} set_property -new 1 form_constraints $fc
set anon_instances true ;# TODO make me configurable
${:object} set_property -new 1 anon_instances $anon_instances
- ${:object} set_property -new 1 auto_correct [[self class] set auto_correct]
+ ${:object} set_property -new 1 auto_correct [[self class] set closed_question_type]
${:object} set_property -new 1 has_solution true
}
@@ -599,7 +614,8 @@
Class create text_interaction -superclass TestItemField -parameter {
}
- #text_interaction set auto_correct false
+ text_interaction set closed_question_type false
+ text_interaction set item_type Text
text_interaction instproc initialize {} {
if {${:__state} ne "after_specs"} return
@@ -661,6 +677,8 @@
Class create short_text_interaction -superclass TestItemField -parameter {
{nr 15}
}
+ short_text_interaction set item_type ShortText
+ short_text_interaction set closed_question_type false
short_text_interaction instproc initialize {} {
if {${:__state} ne "after_specs"} return
@@ -765,7 +783,7 @@
set p [$p info parent]
continue
}
- set :auto_correct [$p set auto_correct]
+ set :auto_correct [$p set closed_question_type]
break
}
@@ -804,14 +822,15 @@
Class create reorder_interaction -superclass TestItemField -parameter {
{nr 15}
}
+ reorder_interaction set item_type {Reorder}
+ reorder_interaction set closed_question_type true
reorder_interaction instproc initialize {} {
if {${:__state} ne "after_specs"} return
#
# Create component structure.
#
set widget [test_item set richtextWidget]
- ns_log notice "[self] [:info class] auto_correct=${:auto_correct}"
:create_components [subst {
{text {$widget,height=100px,label=#xowf.exercise-text#,plugins=OacsFs}}
@@ -874,6 +893,8 @@
{nr 15}
{multiple true}
}
+ mc_interaction2 set item_type {SC MC}
+ mc_interaction2 set closed_question_type true
mc_interaction2 instproc initialize {} {
@@ -952,8 +973,8 @@
set widget [test_item set richtextWidget]
# {correct {boolean_checkbox,horizontal=true,label=#xowf.Correct#,form_item_wrapper_CSSclass=form-inline}}
- :create_components [subst {
- {text {$widget,height=50px,label=#xowf.choice_option#,plugins=OacsFs}}
+ :create_components [subst {
+ {text {$widget,height=50px,label=#xowf.choice_option#,plugins=OacsFs}}
{correct {boolean_checkbox,horizontal=true,default=f,label=#xowf.Correct#,form_item_wrapper_CSSclass=form-inline}}
{solution {textarea,rows=2,label=#xowf.Solution#,form_item_wrapper_CSSclass=form-inline}}
}]
@@ -972,14 +993,15 @@
Class create upload_interaction -superclass TestItemField -parameter {
}
- upload_interaction set auto_correct false
+ upload_interaction set closed_question_type false
+ upload_interaction set item_type Upload
upload_interaction instproc initialize {} {
if {${:__state} ne "after_specs"} {
return
}
set widget [test_item set richtextWidget]
- :create_components [subst {
+ :create_components [subst {
{text {$widget,height=150px,label=#xowf.exercise-text#,plugins=OacsFs}}
{attachments {[:attachments_widget ${:nr_attachments}]}}
}]
@@ -1011,7 +1033,7 @@
${:object} set_property -new 1 form_constraints $fc
set anon_instances true ;# TODO make me configurable
${:object} set_property -new 1 anon_instances $anon_instances
- ${:object} set_property -new 1 auto_correct [[self class] set auto_correct]
+ ${:object} set_property -new 1 auto_correct [[self class] set closed_question_type]
${:object} set_property -new 1 has_solution false
}
}
@@ -1029,6 +1051,8 @@
{multiple true}
{form en:edit-interaction.wf}
}
+ test_section set item_type Composite
+ test_section set closed_question_type false
test_section instproc initialize {} {
@@ -1045,7 +1069,7 @@
# set item_id [${:object} item_id]
# {selection {form_page,form=en:edit-interaction.wf,unless=_item_id=$item_id,multiple=true}}
- :create_components [subst {
+ :create_components [subst {
{text {$widget,height=150px,label=#xowf.exercise-text#,plugins=OacsFs}}
{selection {form_page,form=en:edit-interaction.wf,unless=item_type=Composite,multiple=true}}
}]
@@ -1158,6 +1182,127 @@
}
}
+namespace eval ::xowiki::formfield {
+ ###########################################################
+ #
+ # ::xowiki::formfield::pool_question
+ #
+ ###########################################################
+
+ Class create pool_question -superclass TestItemField -parameter {
+ }
+ pool_question set closed_question_type false ; # the replacement query might be or not autocorrection capabable
+ pool_question set item_type PoolQuestion
+
+ pool_question set item_types {
+ Composite
+ MC
+ Reorder
+ SC
+ ShortText
+ Text
+ Upload
+ }
+ pool_question proc all_item_types_selected {item_types} {
+ #
+ # Check, if all item types were selected
+ #
+ foreach item_type [pool_question set item_types] {
+ if {$item_type ni $item_types} {
+ return 0
+ }
+ }
+ return 1
+ }
+ pool_question instproc initialize {} {
+ if {${:__state} ne "after_specs"} {
+ return
+ }
+ set item_types [pool_question set item_types]
+ set item_type_options [lmap item_type $item_types {
+ list #xowf.menu-New-Item-${item_type}Interaction# $item_type
+ }]
+
+ set current_folder_id [[${:object} parent_id] item_id]
+ set parent_folder_id [::$current_folder_id parent_id]
+ set fi [::xowiki::includelet::folders new -destroy_on_cleanup]
+ set folder_objs [$fi collect_folders \
+ -package_id [${:object} package_id] \
+ -parent_id $parent_folder_id]
+ set folder_options [list]
+ lappend folder_options {*}[lmap folder_obj $folder_objs {
+ if {[$folder_obj parent_id] ne $parent_folder_id} {
+ continue
+ }
+ list [$folder_obj title] ../[$folder_obj name]
+ }]
+
+ dict set pool_dict required true
+ dict set pool_dict options $folder_options
+ dict set pool_dict default ../[::$current_folder_id name]
+ dict set pool_dict label #xowf.pool_question_folder#
+
+ dict set item_dict options $item_type_options
+ dict set item_dict default $item_types
+ dict set item_dict label #xowf.pool_question_item_types#
+
+ :create_components [subst {
+ {folder {[:dict_to_fc -type select $pool_dict]}}
+ {item_types {[:dict_to_fc -type checkbox $item_dict]}}
+ {pattern {text,default=*,label=#xowf.pool_question_pattern#}}
+ }]
+
+ set :__initialized 1
+ }
+
+ pool_question instproc convert_to_internal {} {
+ next
+ set allowed_item_types [:get_named_sub_component_value item_types]
+ dict set fc_dict folder [:get_named_sub_component_value folder]
+ dict set fc_dict pattern [:get_named_sub_component_value pattern]
+ dict set fc_dict item_types $allowed_item_types
+
+ set form ""
+ lappend fc \
+ "@categories:off @cr_fields:hidden" \
+ "answer:[:dict_to_fc -type pool_question_placeholder $fc_dict]"
+
+ ${:object} set_property -new 1 form $form
+ ${:object} set_property -new 1 form_constraints $fc
+
+ #
+ # Turn on "auto_correct" when for every selected item_type, *all*
+ # items are fully closed and therefore suited for
+ # auto_correction. For example, for composite questions, this might
+ # or might not be true (to handle this more aggressively, we would
+ # have to iterate over all exercises of this question types and
+ # check their detailed subcomponents).
+ #
+ set auto_correct 1
+ foreach class [::xowiki::formfield::TestItemField info subclass -closure] {
+ if {[$class exists item_type]} {
+ foreach item_type [$class set item_type] {
+ if {$item_type in $allowed_item_types && ![$class set closed_question_type]} {
+ set auto_correct 0
+ break
+ }
+ }
+ }
+ }
+ #ns_log notice "... FINAL auto_correct $auto_correct (allowed $allowed_item_types)"
+ ${:object} set_property -new 1 auto_correct $auto_correct
+ }
+
+ Class create pool_question_placeholder -superclass {TestItemField} -parameter {
+ folder
+ pattern
+ item_types
+ }
+
+}
+
+
+
############################################################################
# Generic Assement interface
############################################################################
@@ -1449,6 +1594,7 @@
# from left to right, changing types, etc., which is not
# supported here.
#
+ set result ""
foreach fc $form_constraints {
#ns_log notice "... fc_to_dict works on <$fc>"
if {[regexp {^([^:]+):(.*)$} $fc _ field_name definition]} {
@@ -3914,6 +4060,377 @@
#----------------------------------------------------------------------
# Class: Question_manager
+ # Method: initialize
+ #----------------------------------------------------------------------
+ :public method initialize {-wfi:object} {
+ #
+ # Initialize the question manager for a certain workflow
+ # instance. This is needed for per-answer-workflow questions (as
+ # for pool questions, where different questions are taken for
+ # different users).
+ #
+
+ #ns_log notice "QM initialize wfi $wfi"
+ set isAnswerInstance [expr {[$wfi is_wf_instance] == 1 && [$wfi is_wf] == 0}]
+ if {$isAnswerInstance} {
+ #ns_log notice "QM initialize answer instance [$wfi name] // [$wfi instance_attributes]"
+ set :wfi $wfi
+ } else {
+ ns_log warning "initializing question manager for not an answer instance [$wfi name]" \
+ "// [$wfi instance_attributes]"
+ }
+ }
+
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
+ # Method: get_pool_replacement_candidates
+ #----------------------------------------------------------------------
+ :method get_pool_replacement_candidates {
+ {-allowed_forms en:edit-interaction.wf}
+ {-minutes}
+ {-points}
+ {-fc_dict}
+ {-lang ""}
+ pool_question_obj
+ } {
+ #
+ # Obtain for the specs in the pool_question_obj potential
+ # replacement items.
+ #
+ set parent_id [$pool_question_obj parent_id]
+ set package_id [$pool_question_obj package_id]
+
+ #
+ # We want to select only instances of these edit workflows
+ # specified in allowed_forms.
+ #
+ set form_objs [::$package_id instantiate_forms \
+ -parent_id $parent_id \
+ -forms $allowed_forms]
+ set form_object_item_ids [lmap f $form_objs {$f item_id}]
+
+ set pattern [dict get $fc_dict pattern]
+ set item_types [dict get $fc_dict item_types]
+ set folder [dict get $fc_dict folder]
+
+ set item_ref_info [::$package_id item_ref \
+ -use_package_path 0 \
+ -default_lang en \
+ -parent_id $parent_id \
+ $folder]
+ set folder_id [:dict_value $item_ref_info item_id]
+
+ #
+ # In case, all item types are selected, no additional clauses
+ # are needed.
+ #
+ if {[::xowiki::formfield::pool_question all_item_types_selected $item_types]} {
+ set w_clauses ""
+ } else {
+ set w_clauses [list "item_type = [join $item_types |]"]
+ }
+
+ #
+ # Never include PoolQuestions as a replacement for a pool
+ # question.
+ #
+ set u_clauses [list "item_type = PoolQuestion"]
+
+ #
+ # Perform language selection based on the name and combine this
+ # with the provided pattern.
+ #
+ if {$pattern eq ""} {
+ set pattern *
+ }
+ if {$lang ne ""} {
+ lappend w_clauses "_name matches ${lang}:$pattern"
+ } elseif {$pattern ne "*"} {
+ lappend w_clauses "_name matches $pattern"
+ } else {
+ #
+ # In case thjere is no pattern and no lang provided, there is
+ # no filter necessary.
+ #
+ }
+
+ # The matching of minutes and points are more complex due to
+ # mutual completion (see below).
+ #
+ #if {$minutes ne ""} {
+ # lappend w_clauses "question matches *question.minutes $minutes*"
+ #}
+
+ set filters [::xowiki::FormPage compute_filter_clauses \
+ {*}[expr {[llength $u_clauses] > 0 ? [list -unless [join $u_clauses &&]] : ""}] \
+ {*}[expr {[llength $w_clauses] ? [list -where [join $w_clauses &&]] : ""}] \
+ ]
+
+ #ns_log notice "get_pool_replacement_candidates filters $filters"
+ #ns_log notice "get_pool_replacement_candidates filters WC $w_clauses -->\n[dict get $filters wc]"
+ #ns_log notice "get_pool_replacement_candidates filters UC $u_clauses -->\n[dict get $filters uc]"
+
+ #
+ # In case the folder_id is a symbolic link to a different
+ # folder, resolve the link and reset the folder_id to the
+ # item_id of the link target.
+ #
+ # In case we have links to different packages, some more work
+ # might be required (e.g. instantiate the other package, etc.).
+ #
+ if {![nsf::is object ::$folder_id]} {
+ ::xo::db::CrClass get_instance_from_db -item_id $folder_id
+ }
+ if {[::$folder_id is_link_page]} {
+ set targetObj [::$folder_id get_target_from_link_page]
+ set folder_id [$targetObj item_id]
+ }
+
+ #
+ # TODO: one has to check the performance of the generic
+ # get_form_entries on learn with larger question pools. It would
+ # be possible to provide a quicker query based on the
+ # xowiki*item_index joined with acs-objects instead of the
+ # generic view used in get_form_entries. ... but maybe the
+ # current approach with caching is already quick enough.
+ #
+ set items [::xowiki::FormPage get_form_entries \
+ -base_item_ids ${form_object_item_ids} \
+ -form_fields {} \
+ -publish_status ready \
+ -parent_id $folder_id \
+ -package_id ${package_id} \
+ -h_where [dict get $filters wc] \
+ -h_unless [dict get $filters uc] \
+ -initialize false \
+ -from_package_ids ""]
+
+ ns_log notice "get_pool_replacement_candidates parent_id $folder_id -> [llength [$items children]]"
+
+ #
+ # Since we allow the user to specify either minutes or points,
+ # and use the specified values as defaults for the others, we
+ # have to replace the empty values with the defaults (mutual
+ # completion).
+ #
+ if {$minutes eq "" && $points ne ""} {
+ set minutes $points
+ } elseif {$minutes ne "" && $points eq ""} {
+ set points $minutes
+ }
+
+ set result ""
+ foreach item [$items children] {
+ set qn [:qualified_question_names $item]
+ set ia [$item set instance_attributes]
+ set qa [dict get $ia question]
+
+ #
+ # Replace empty values for "minutes" and "points" with the
+ # defaults before comparing.
+ #
+ set item_minutes [dict get $qa question.minutes]
+ set item_points [dict get $qa question.points]
+ if {$item_minutes eq "" && $item_points ne ""} {
+ set item_minutes $item_points
+ } elseif {$item_minutes ne "" && $item_points eq ""} {
+ set item_points $item_minutes
+ }
+ if {$minutes ne "" && $item_minutes ne $minutes} {
+ continue
+ } elseif {$points ne "" && $item_points ne $points} {
+ continue
+ }
+
+ dict set result $qn item_id [$item item_id]
+ dict set result $qn item_type [dict get $ia item_type]
+ #dict set result $qn question_dict $qa
+ }
+
+ #ns_log notice "=============== get_pool_replacement_candidates returns $result"
+ return $result
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
+ # Method: get_pool_questions
+ #----------------------------------------------------------------------
+ :public method get_pool_questions {
+ {-allowed_forms en:edit-interaction.wf}
+ {-field_name ""}
+ pool_question_obj
+ exam_question_names
+ } {
+ #
+ # Obtain for the specs in the pool_question_obj potential
+ # replacement items in form of a replacement dict. For raw forms
+ # (i.e., not obtained via the renaming form-loader), we have just
+ # the plain "answer", which can be provided via the "field_name"
+ # attribute.
+ #
+ set query_dict [fc_to_dict [$pool_question_obj property form_constraints]]
+ if {$field_name eq ""} {
+ #
+ # No field name was provided, so get the field name from the
+ # question obj.
+ #
+ set field_name [::xowf::test_item::renaming_form_loader \
+ form_name_based_attribute_stem [$pool_question_obj name]]
+ if {![dict exists $query_dict $field_name]} {
+ #
+ # Fall back to field_name "answer". This will be necessary,
+ # when called with question_objs not adapted by the renaming
+ # form-loader.
+ #
+ if {[dict exists $query_dict answer]} {
+ ns_log notice "get_pool_questions: fallback from field_name '$field_name' to 'answer'"
+ set field_name answer
+ }
+ }
+ }
+ set question_attributes [dict get [$pool_question_obj instance_attributes] question]
+ set minutes [dict get $question_attributes question.minutes]
+ set points [dict get $question_attributes question.points]
+
+ set fc_dict [dict get $query_dict $field_name]
+ set lang [string range [$pool_question_obj nls_language] 0 1]
+
+ append key test-item-replacement-cands \
+ - $minutes - $points - $lang - $fc_dict - [$pool_question_obj revision_id]
+ ns_log notice "get_pool_questions fetch via key: '$key'"
+
+ #return [:get_pool_replacement_candidates \
+ -minutes $minutes \
+ -points $points \
+ -fc_dict $fc_dict \
+ -lang $lang \
+ $pool_question_obj]
+ return [ns_cache_eval -expires 1m -- ns:memoize $key {
+ :get_pool_replacement_candidates \
+ -minutes $minutes \
+ -points $points \
+ -fc_dict $fc_dict \
+ -lang $lang \
+ $pool_question_obj
+ }]
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
+ # Method: replace_pool_question
+ #----------------------------------------------------------------------
+ :public method replace_pool_question {
+ -position
+ -seed
+ {-allowed_forms en:edit-interaction.wf}
+ {-field_name ""}
+ -pool_question_obj
+ -exam_question_names
+ } {
+ #
+ #
+ # @return an initialized replacement form obj if this is possible
+ #
+ set field_name ""; ## rely on fallback
+ set candidate_dict [:get_pool_questions \
+ -allowed_forms $allowed_forms \
+ -field_name $field_name \
+ $pool_question_obj \
+ $exam_question_names]
+
+ set candidate_names [dict keys $candidate_dict]
+ set nrCandidates [llength $candidate_names]
+ if {$nrCandidates == 0} {
+ set h [ns_set iget [ns_conn headers] referrer]
+ set url [join [lrange [split [xo::cc url] /] 0 end-1] /]?m=edit
+ util_user_message -message "could not find a replacement item for pool question: no matching item found"
+ ad_returnredirect $url
+ ad_script_abort
+ }
+
+ #
+ # It might be the case that we select the same item for an exam
+ # twice. Therefore, we have to iterate, until we find different
+ # items.
+ #
+ expr {srand($seed)}
+ set maxiter 100
+ while {1} {
+ set i [expr {int(($nrCandidates) * rand())}]
+ set new_name [lindex $candidate_names $i]
+ #ns_log notice "replace_pool_question position $position seed $seed random_index $i"
+
+ set contained [expr {$new_name in $exam_question_names}]
+ #ns_log notice "replace_pool_question replace [$pool_question_obj name] by $new_name contained in" \
+ # "[lsort $exam_question_names] contained $contained"
+ if {!$contained || [incr maxiter -1] < 0} {
+ break
+ }
+ }
+ if {$contained} {
+ error "could not find a replacement item for [$pool_question_obj name]: only duplicate items found"
+
+ }
+ set form_obj [::xo::db::CrClass get_instance_from_db \
+ -item_id [dict get $candidate_dict $new_name item_id]]
+
+ #$form_obj initialize
+
+ # ns_log notice [$form_obj serialize]
+ return $form_obj
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
+ # Method: replace_pool_questions
+ #----------------------------------------------------------------------
+ :public method replace_pool_questions {
+ -answer_obj:object
+ -exam_obj:object
+ } {
+ if {[$answer_obj property question] ne ""} {
+ ns_log notice "answer_obj $answer_obj has already a 'question' property" \
+ [lsort [dict keys [$answer_obj instance_attributes]]]
+ return
+ }
+ set exam_question_names [$exam_obj property question]
+ set form_objs [:load_question_objs $exam_obj $exam_question_names]
+
+ #
+ # Make sure to normalize all names to ease comparison
+ #
+ set original_question_names [:qualified_question_names $form_objs]
+
+ set replaced_form_objs {}
+ set position 0
+ set seeds [$answer_obj property seeds]
+ foreach form_obj $form_objs {
+ #ns_log notice "YYY check item_type '[$form_obj property item_type]' // [$form_obj instance_attributes]"
+ if {[$form_obj property item_type] eq "PoolQuestion"} {
+ set replaced_form_obj [:replace_pool_question \
+ -position $position \
+ -seed [lindex $seeds $position] \
+ -pool_question_obj $form_obj \
+ -exam_question_names $exam_question_names]
+ set exam_question_names [lreplace $exam_question_names $position $position \
+ [:qualified_question_names $replaced_form_obj]]
+ lappend replaced_form_objs $replaced_form_obj
+ } else {
+ lappend replaced_form_objs $form_obj
+ }
+ incr position
+ }
+ #ns_log notice "YYYY OLD NAMES [join $original_question_names { }]"
+ #ns_log notice "YYYY UPD NAMES [join $exam_question_names { }]"
+ if {$original_question_names ne $exam_question_names} {
+ ns_log notice "YYYY store question names in user's answer workflow"
+ $answer_obj set_property -new 1 question $exam_question_names
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
# Method: goto_page
#----------------------------------------------------------------------
:public method goto_page {obj:object position} {
@@ -4028,42 +4545,67 @@
#----------------------------------------------------------------------
# Class: Question_manager
+ # Method: qualified_question_names
+ #----------------------------------------------------------------------
+ :method qualified_question_names {question_objs} {
+ #
+ # Return the question names with parent folder in form of an
+ # item-ref. We assume here, all question_objs are from the same
+ # xowf instance. We will need item-refs pointing to other
+ # instances in the future.
+ #
+ lmap question_obj $question_objs {
+ set parent_id [$question_obj parent_id]
+ if {![nsf::is object ::$parent_id]} {
+ ::xo::db::CrClass get_instance_from_db -item_id $parent_id
+ }
+ set ref [::$parent_id name]/[$question_obj name]
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
# Method: load_question_objs
#----------------------------------------------------------------------
:method load_question_objs {obj:object names} {
#
# Load the question objects for the provided question names and
# return the question objs.
#
- set questions [lmap ref $names {
- if {![string match "*/*" $ref]} {
- #
- # In case, '$ref' refers to a site-wide page, a prefix with
- # the parent name would not help. In these cases, we expect
- # to have the parent obj not instantiated.
- #
- set parent_id [$obj parent_id]
- if {[nsf::is object ::$parent_id]} {
+
+ set parent_id [$obj parent_id]
+ #
+ # Make sure to have names pointing to a folder.
+ # In case, '$ref' refers to a site-wide page, a prefix with
+ # the parent name would not help. In these cases, we expect
+ # to have the parent obj not instantiated.
+ #
+ if {[nsf::is object ::$parent_id]} {
+ set names [lmap ref $names {
+ if {![string match "*/*" $ref]} {
set ref [::$parent_id name]/$ref
}
- }
- set ref
- }]
- set questionNames [join $questions |]
+ set ref
+ }]
+ }
+ #ns_log notice "XXX [$obj name] load_question_objs names = <$names>"
+ #xo::show_stack
+ set questionNames [join $names |]
set questionForms [::[$obj package_id] instantiate_forms \
-default_lang [$obj lang] \
-forms $questionNames]
#ns_log notice "load_question_objs called with $obj $names -> $questionForms"
- if {[llength $questionForms] < [llength $questions]} {
- if {[llength $questions] == 1} {
- ns_log warning "load_question_objs: question '$questions' could not be loaded"
+ if {[llength $questionForms] < [llength $names]} {
+ if {[llength $names] == 1} {
+ ns_log warning "load_question_objs: question '$names' could not be loaded"
} else {
set loaded [llength $questionForms]
- set out_of [llength $questions]
- ns_log warning "load_question_objs: only $loaded out of $out_of from '$questions' could be loaded"
+ set out_of [llength $names]
+ ns_log warning "load_question_objs: only $loaded out of $out_of from '$names' could be loaded"
}
}
+ #ns_log notice "XXX [$obj name] load_question_objs questionNames = <$names>"
return $questionForms
}
@@ -4072,8 +4614,8 @@
# Method: current_question_name
#----------------------------------------------------------------------
:method current_question_name {obj:object} {
- set questions [dict get [$obj instance_attributes] question]
- return [lindex [dict get [$obj instance_attributes] question] [$obj property position]]
+ set questions [:question_names $obj]
+ return [lindex $questions [$obj property position]]
}
#----------------------------------------------------------------------
@@ -4114,7 +4656,9 @@
# objects in the right order, depending on the shuffle_id.
#
:assert_assessment $obj
- set form_objs [:load_question_objs $obj [$obj property question]]
+ set form_objs [:load_question_objs $obj [:question_names $obj]]
+ #ns_log notice "question_objs from $obj => $form_objs shuffle_id $shuffle_id"
+
if {$shuffle_id > -1} {
set result {}
foreach i [::xowiki::randomized_indices -seed $shuffle_id [llength $form_objs]] {
@@ -4133,7 +4677,20 @@
#
# Return the names of the questions of an assessment.
#
- return [$obj property question]
+ if {[info exists :wfi]} {
+ if {![nsf::is object ${:wfi}]} {
+ ns_log notice "we cannot trust :wfi '${:wfi}', probably a leftover"
+ unset :wfi
+ }
+ }
+ if {[info exists :wfi] && [${:wfi} property question] ne ""} {
+ set names [${:wfi} property question]
+ #ns_log notice "question_names returns obj-specific $names"
+ } else {
+ set names [$obj property question]
+ #ns_log notice "question_names returns wf-names ($obj property)"
+ }
+ return $names
}
#----------------------------------------------------------------------
@@ -4146,7 +4703,7 @@
# number of defined questions, or it might be restricted by the
# property max_items (if defined for "obj").
#
- set nr_questions [llength [$obj property question]]
+ set nr_questions [llength [:question_names $obj]]
set max_items [$obj property max_items ""]
if {$max_items ne ""} {
if {$max_items < $nr_questions} {
@@ -4185,7 +4742,8 @@
# position).
#
:assert_assessment $obj
- set questions [dict get [$obj instance_attributes] question]
+ #set questions [dict get [$obj instance_attributes] question]
+ set questions [:question_names $obj]
set result [:load_question_objs $obj [lindex $questions $position]]
return $result
}
@@ -4487,8 +5045,7 @@
#
# No question should have shuffle "always".
#
- if {[dict exists $qd question.shuffle]
- && [dict get $qd question.shuffle] eq "always"} {
+ if {[:dict_value $qd question.shuffle] eq "always"} {
#ns_log notice "FOUND shuffle $qd"
set randomizationOk 0
}
@@ -4498,18 +5055,20 @@
#
if {[dict exists $qd question.grading]} {
#
- # autograde ok on the question level
+ # autograde ok on the item type level
#
} elseif {[:dict_value $formAttributes auto_correct 0]} {
#
- # autograde ok on the form level
+ # auto_correct is in principle enabled, check details on
+ # the concrete question item.
#
- # Check, if the correct_when specification of a short text
- # question is suited for autocorrection. On the longer
- # range, this function should be moved to a different
- # place.
- #
- if {[dict exists $formAttributes item_type] && [dict get $formAttributes item_type] eq "ShortText"} {
+ if {[:dict_value $formAttributes item_type] eq "ShortText"} {
+ #
+ # Check, if the correct_when specification of a short text
+ # question is suited for autocorrection. On the longer
+ # range, this function should be moved to a different
+ # place.
+ #
set dict [lindex [fc_to_dict [dict get $formAttributes form_constraints]] 1]
foreach a [dict get $dict answer] {
set op ""
@@ -4542,7 +5101,7 @@
} else {
set autoGrade 0
}
- ns_log notice "question_info [$form_obj name] [$form_obj title] autoGrade $autoGrade"
+ #ns_log notice "question_info [$form_obj name] [$form_obj title] autoGrade $autoGrade"
}
}
@@ -4635,10 +5194,10 @@
# @param user_answers instance of the answer-wf.
# Needed for user-specific percent substitutions.
- #ns_log notice "combined_question_form called with user_answers <$user_answers>"
+ #ns_log notice "combined_question_form called with user_answers <$user_answers> for $obj [$obj name]"
#if {$user_answers eq ""} {xo::show_stack}
- set all_form_objs [:question_objs -shuffle_id $shuffle_id $obj]
+ set all_form_objs [:question_objs -shuffle_id $shuffle_id $obj]
set positions {}
if {[llength $form_objs] > 0} {
foreach form_obj $form_objs {
@@ -4734,11 +5293,51 @@
return #xowf.shuffle_$m#
}
}
+
#----------------------------------------------------------------------
# Class: Question_manager
+ # Method: render_describe_infos
+ #----------------------------------------------------------------------
+ :method render_describe_infos {describe_infos} {
+ set msgList {}
+ foreach describe_info $describe_infos {
+ if {$describe_info ne ""} {
+ #
+ # The handled metrics are currently hardcoded here. So, we can
+ # rely on having the returned value in the message keys. The
+ # list order is important, since it determines also the ordering
+ # in the message.
+ #
+ set msg ""
+ set hasStructure [dict exists $describe_info question_structure]
+ set metrics [expr {$hasStructure ? "question_structure" : [list choice_options sub_questions]}]
+ lappend metrics nrcorrect Minutes Points shuffle available_pool_items available_pool_item_stats
+ foreach metric $metrics {
+ if {[:dict_value $describe_info $metric] ne ""} {
+ set m [dict get $describe_info $metric]
+ switch $metric {
+ nrcorrect { append msg [:pretty_ncorrect $m] }
+ shuffle { append msg "#xowf.Shuffle#: [:pretty_shuffle $m]" }
+ default { append msg "#xowf.$metric#: $m "}
+ }
+ }
+ }
+ #append msg " $describe_info
"
+ lappend msgList "$msg\n"
+ }
+ }
+ return $msgList
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
# Method: describe_form
#----------------------------------------------------------------------
- :public method describe_form {{-asHTML:switch} form_obj} {
+ :public method describe_form {
+ {-asHTML:switch}
+ {-field_name ""}
+ form_obj
+ } {
#
# Call for every form field of the form_obj the "describe"
# method and return these infos in a form of a list.
@@ -4759,42 +5358,19 @@
set form_fields [$form_obj create_form_fields_from_form_constraints \
-lookup $fc]
- set question_infos [lmap form_field $form_fields { $form_field describe }]
+ set describe_infos [lmap form_field $form_fields {
+ $form_field describe -field_name $field_name
+ }]
#ns_log notice "describe_form [$form_obj name]: $question_infos"
- set question_infos [:pretty_nr_alternatives $question_infos]
+ set describe_infos [:pretty_nr_alternatives $describe_infos]
if {!$asHTML} {
- return $question_infos
+ #ns_log notice "OOO [$form_obj name] early exit $describe_infos"
+ return $describe_infos
+ } else {
+ set HTML [:render_describe_infos $describe_infos]
+ return $HTML
}
-
- set msgList {}
- foreach question_info $question_infos {
- if {$question_info ne ""} {
- #
- # The handled metrics are currently hardcoded here. So, we can
- # rely on having the returned value in the message keys. The
- # list order is important, since it determines also the ordering
- # in the message.
- #
- set msg ""
- set hasStructure [dict exists $question_info question_structure]
- set metrics [expr {$hasStructure ? "question_structure" : [list choice_options sub_questions]}]
- lappend metrics nrcorrect Minutes Points shuffle
- foreach metric $metrics {
- if {[dict exists $question_info $metric]} {
- set m [dict get $question_info $metric]
- switch $metric {
- nrcorrect { append msg [:pretty_ncorrect $m] }
- shuffle { append msg "#xowf.Shuffle#: [:pretty_shuffle $m]" }
- default { append msg "#xowf.$metric#: $m "}
- }
- }
- }
- #append msg " $question_info
"
- lappend msgList "$msg\n"
- }
- }
- return $msgList
}
#----------------------------------------------------------------------
@@ -4837,17 +5413,28 @@
foreach form_obj $form_objs {
set chunk [lindex [:describe_form $form_obj] 0]
set structure ""
- foreach att {question_structure choice_options sub_questions} {
+ foreach att {
+ question_structure choice_options sub_questions
+ } {
if {[dict exists $chunk $att]} {
append structure [dict get $chunk $att]
break
}
}
+ if {[dict exists $chunk available_pool_items]} {
+ append structure \
+ " " [dict get $chunk available_pool_items] " " #xowf.questions# \
+ " " ([dict get $chunk available_pool_item_stats])
+ }
if {[dict exists $chunk nrcorrect]} {
append structure " " [:pretty_ncorrect [dict get $chunk nrcorrect]]
}
- if {[$obj state] in {done submission_review}} {
- dict set chunk title_value "[ns_quotehtml [$form_obj title]]"
+ if {[$obj state] in {done submission_review}
+ && ![dict exists $chunk available_pool_items]
+ } {
+ dict set chunk title_value [subst {
+ [ns_quotehtml [$form_obj title]]
+ }]
} else {
dict set chunk title_value [ns_quotehtml [$form_obj title]]
}
@@ -4974,6 +5561,7 @@
set nrQuestions [llength $question_objs]
set randomizationOk [dict get $combined_form_info randomization_for_exam]
set autograde [dict get $combined_form_info autograde]
+
set revision_sets [$obj get_revision_sets]
set published_periods [xowf::test_item::answer_manager state_periods $revision_sets -state published]
set review_periods [xowf::test_item::answer_manager state_periods $revision_sets -state submission_review]
@@ -5248,13 +5836,14 @@
# being, until we have a better understanding what's needed in
# detail.
#
- ::xowiki::formfield::FormField instproc describe {} {
+ ::xowiki::formfield::FormField instproc describe {{-field_name ""}} {
set d ""
#
# The dict keys of the result should correspond as far as possible
# to message keys to ease multi-language communication.
#
set qa [${:object} property question]
+ #ns_log notice "FormField describe gets <$qa> from ${:object}"
foreach {key name} {
question.minutes Minutes
@@ -5285,7 +5874,6 @@
dict set d nrcorrect [llength [lsearch -exact -all ${answer} t]]
dict set d shuffle ${:shuffle_kind}
#dict set d all [:serialize]
- #ns_log warning "describe: $d"
}
::xowiki::formfield::text_fields {
set type ShortText
@@ -5304,18 +5892,32 @@
dict set d all ${:spec}
dict set d sub_questions [llength ${options}]
dict set d shuffle ${:shuffle_kind}
- #ns_log warning "describe: $d"
}
::xowiki::formfield::textarea {
set type Text
}
+ ::xowiki::formfield::pool_question_placeholder {
+ set type PoolQuestion
+ set item_dict [::xowf::test_item::question_manager get_pool_questions \
+ -field_name $field_name ${:object} ""]
+ set counts ""
+ foreach {key value} $item_dict {
+ dict incr counts [dict get $item_dict $key item_type]
+ }
+
+ dict set d available_pool_items [dict size $item_dict]
+ dict set d available_pool_item_stats $counts
+ }
+
default {
set type [:info class]
ns_log warning "describe: class [:info class] not handled"
}
}
dict set d type $type
+ #ns_log notice "describe [:info class] [${:object} name] -> $d"
+
return $d
}
}
@@ -5528,6 +6130,7 @@
{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.Item.CompositeInteraction -form en:edit-interaction.wf -query p.item_type=Composite}
+ {entry -name New.Item.PoolQuestionInteraction -form en:edit-interaction.wf -query p.item_type=PoolQuestion}
{entry -name New.App.OnlineExam -form en:online-exam.wf -disabled true}
{entry -name New.App.InclassQuiz -form en:inclass-quiz.wf -disabled true}
Index: openacs-4/packages/xowf/tcl/xowf-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/xowf-procs.tcl,v
diff -u -N -r1.28.2.60 -r1.28.2.61
--- openacs-4/packages/xowf/tcl/xowf-procs.tcl 31 Oct 2021 19:55:38 -0000 1.28.2.60
+++ openacs-4/packages/xowf/tcl/xowf-procs.tcl 15 Nov 2021 17:02:38 -0000 1.28.2.61
@@ -57,6 +57,7 @@
TestItemReorder.form
TestItemUpload.form
TestItemComposite.form
+ TestItemPoolQuestion.form
ExamFolder
@@ -655,6 +656,7 @@
} else {
set source_obj [${:object} page_template]
}
+
set revision_id [$source_obj revision_id]
if {$revision_id == 0} {
set revision_id [::xo::db::sql::content_item get_live_revision \
@@ -1378,7 +1380,14 @@
WorkflowPage ad_instproc render_icon {} {
Provide an icon or text for describing the kind of application.
} {
- if {[:is_wf_instance]} {
+ if {[:info procs render_icon] ne ""} {
+ #
+ # In case, we have a per-object method (i.e., defined via the
+ # workflow), use this with highest precedence.
+ #
+ next
+
+ } elseif {[:is_wf_instance]} {
set page_template ${:page_template}
set title [::$page_template title]
regsub {[.]wf$} $title "" title
Index: openacs-4/packages/xowiki/xowiki.info
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/xowiki.info,v
diff -u -N -r1.180.2.75 -r1.180.2.76
--- openacs-4/packages/xowiki/xowiki.info 31 Oct 2021 19:07:53 -0000 1.180.2.75
+++ openacs-4/packages/xowiki/xowiki.info 15 Nov 2021 17:02:36 -0000 1.180.2.76
@@ -10,7 +10,7 @@
t
xowiki
-
+
Gustaf Neumann
A xotcl-based enterprise wiki system with multiple object types
2021-09-15
@@ -55,7 +55,7 @@
BSD-Style
2
-
+