Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
integration
prechecker
Commits
5c97a8da
Commit
5c97a8da
authored
Aug 20, 2015
by
Tim Hunt
Browse files
MDL-51090 question: further refinements to validating manual grades
parent
df0c2e95
Changes
10
Hide whitespace changes
Inline
Side-by-side
mod/quiz/tests/behat/
validate_
manual_mark_
correct_format
.feature
→
mod/quiz/tests/behat/manual
ly
_mark_
question
.feature
View file @
5c97a8da
@mod
@mod_quiz
Feature
:
In or
de
r
t
o mannually mark a
question
I want
Feature
:
Teachers can overri
de t
he grade for any
question
As a teacher
I must be able to set the marks I want on the Rapport question page.
In order to correct errors
I must be able to override the grades that Moodle gives.
Background
:
Given the following "users" exist
:
...
...
@@ -18,22 +19,21 @@ Feature: In order to mannually mark a question I want
And the following "question categories" exist
:
|
contextlevel
|
reference
|
name
|
|
Course
|
C1
|
Test
questions
|
Given
the following "questions" exist
:
And
the following "questions" exist
:
|
questioncategory
|
qtype
|
name
|
questiontext
|
defaultmark
|
|
Test
questions
|
essay
|
TF1
|
First
question
|
20
|
And the following "activities" exist
:
|
activity
|
name
|
intro
|
course
|
idnumber
|
grade
|
|
quiz
|
Quiz
1
|
Quiz
1
description
|
C1
|
quiz1
|
20
|
And quiz "Quiz 1" contains the following questions
:
|
question
|
page
|
requireprevious
|
|
TF1
|
1
|
0
|
|
question
|
page
|
|
TF1
|
1
|
And
I log in as
"student1"
And
I follow
"Course 1"
And
I follow
"Quiz 1"
And
I press
"Attempt quiz now"
And
I follow
"Finish attempt ..."
And
I press
"Submit all and finish"
# Pop-up confirmation
And
I click on
"Submit all and finish"
"button"
in the
"Confirmation"
"dialogue"
And
I log out
And
I log in as
"teacher1"
...
...
@@ -51,9 +51,7 @@ Feature: In order to mannually mark a question I want
Then
I should see
"This grade is outside the valid range."
And
I set the field
"Mark"
to
"aa"
And
I press
"Save"
Then
I should see
"That is not a valid number."
And
I set the field
"Mark"
to
"10"
And
I should see
"That is not a valid number."
And
I set the field
"Mark"
to
"10
.0
"
And
I press
"Save"
Then
I should see
"Changes saved"
And
I should see
"Changes saved"
question/behaviour/behaviourbase.php
View file @
5c97a8da
...
...
@@ -194,7 +194,7 @@ abstract class question_behaviour {
$vars
=
array
(
'comment'
=>
PARAM_RAW
,
'commentformat'
=>
PARAM_INT
);
if
(
$this
->
qa
->
get_max_mark
())
{
$vars
[
'mark'
]
=
question_attempt
::
PARAM_MARK
;
$vars
[
'mark'
]
=
PARAM_RAW_TRIMMED
;
$vars
[
'maxmark'
]
=
PARAM_FLOAT
;
}
return
$vars
;
...
...
@@ -467,15 +467,25 @@ abstract class question_behaviour {
}
if
(
$pendingstep
->
has_behaviour_var
(
'mark'
))
{
$fraction
=
$pendingstep
->
get_behaviour_var
(
'mark'
)
/
$pendingstep
->
get_behaviour_var
(
'maxmark'
);
if
(
$pendingstep
->
get_behaviour_var
(
'mark'
)
===
''
)
{
$mark
=
question_utils
::
clean_param_mark
(
$pendingstep
->
get_behaviour_var
(
'mark'
));
if
(
$mark
===
null
)
{
throw
new
coding_exception
(
'Inalid number format '
.
$pendingstep
->
get_behaviour_var
(
'mark'
)
.
' when processing a manual grading action.'
,
'Question '
.
$this
->
question
->
id
.
', slot '
.
$this
->
qa
->
get_slot
());
}
else
if
(
$mark
===
''
)
{
$fraction
=
null
;
}
else
if
(
$fraction
>
$this
->
qa
->
get_max_fraction
()
||
$fraction
<
$this
->
qa
->
get_min_fraction
())
{
throw
new
coding_exception
(
'Score out of range when processing '
.
'a manual grading action.'
,
'Question '
.
$this
->
question
->
id
.
', slot '
.
$this
->
qa
->
get_slot
()
.
', fraction '
.
$fraction
);
}
else
{
$fraction
=
$pendingstep
->
get_behaviour_var
(
'mark'
)
/
$pendingstep
->
get_behaviour_var
(
'maxmark'
);
if
(
$fraction
>
$this
->
qa
->
get_max_fraction
()
||
$fraction
<
$this
->
qa
->
get_min_fraction
())
{
throw
new
coding_exception
(
'Score out of range when processing '
.
'a manual grading action.'
,
'Question '
.
$this
->
question
->
id
.
', slot '
.
$this
->
qa
->
get_slot
()
.
', fraction '
.
$fraction
);
}
}
$pendingstep
->
set_fraction
(
$fraction
);
}
...
...
question/behaviour/manualgraded/tests/walkthrough_test.php
View file @
5c97a8da
...
...
@@ -384,4 +384,98 @@ class qbehaviour_manualgraded_walkthrough_testcase extends qbehaviour_walkthroug
$this
->
displayoptions
->
manualcomment
=
question_display_options
::
VISIBLE
;
$this
->
check_output_contains
(
'This should only appear if the displya options allow it'
);
}
public
function
test_manual_graded_invalid_value_throws_exception
()
{
global
$PAGE
;
// The current text editor depends on the users profile setting - so it needs a valid user.
$this
->
setAdminUser
();
// Required to init a text editor.
$PAGE
->
set_url
(
'/'
);
// Create an essay question.
$essay
=
test_question_maker
::
make_an_essay_question
();
$this
->
start_attempt_at_question
(
$essay
,
'deferredfeedback'
,
10
);
// Check the right model is being used.
$this
->
assertEquals
(
'manualgraded'
,
$this
->
quba
->
get_question_attempt
(
$this
->
slot
)
->
get_behaviour_name
());
// Check the initial state.
$this
->
check_current_state
(
question_state
::
$todo
);
$this
->
check_current_mark
(
null
);
$this
->
check_current_output
(
$this
->
get_contains_question_text_expectation
(
$essay
),
$this
->
get_does_not_contain_feedback_expectation
());
// Simulate some data submitted by the student.
$this
->
process_submission
(
array
(
'answer'
=>
'This is my wonderful essay!'
,
'answerformat'
=>
FORMAT_HTML
));
// Verify.
$this
->
check_current_state
(
question_state
::
$complete
);
$this
->
check_current_mark
(
null
);
$this
->
check_current_output
(
new
question_contains_tag_with_attribute
(
'textarea'
,
'name'
,
$this
->
quba
->
get_question_attempt
(
$this
->
slot
)
->
get_qt_field_name
(
'answer'
)),
$this
->
get_does_not_contain_feedback_expectation
());
// Finish the attempt.
$this
->
quba
->
finish_all_questions
();
// Verify.
$this
->
check_current_state
(
question_state
::
$needsgrading
);
$this
->
check_current_mark
(
null
);
$this
->
assertEquals
(
'This is my wonderful essay!'
,
$this
->
quba
->
get_response_summary
(
$this
->
slot
));
// Try to process a an invalid grade.
$this
->
setExpectedException
(
'coding_exception'
);
$this
->
manual_grade
(
'Comment'
,
'frog'
,
FORMAT_HTML
);
}
public
function
test_manual_graded_out_of_range_throws_exception
()
{
global
$PAGE
;
// The current text editor depends on the users profile setting - so it needs a valid user.
$this
->
setAdminUser
();
// Required to init a text editor.
$PAGE
->
set_url
(
'/'
);
// Create an essay question.
$essay
=
test_question_maker
::
make_an_essay_question
();
$this
->
start_attempt_at_question
(
$essay
,
'deferredfeedback'
,
10
);
// Check the right model is being used.
$this
->
assertEquals
(
'manualgraded'
,
$this
->
quba
->
get_question_attempt
(
$this
->
slot
)
->
get_behaviour_name
());
// Check the initial state.
$this
->
check_current_state
(
question_state
::
$todo
);
$this
->
check_current_mark
(
null
);
$this
->
check_current_output
(
$this
->
get_contains_question_text_expectation
(
$essay
),
$this
->
get_does_not_contain_feedback_expectation
());
// Simulate some data submitted by the student.
$this
->
process_submission
(
array
(
'answer'
=>
'This is my wonderful essay!'
,
'answerformat'
=>
FORMAT_HTML
));
// Verify.
$this
->
check_current_state
(
question_state
::
$complete
);
$this
->
check_current_mark
(
null
);
$this
->
check_current_output
(
new
question_contains_tag_with_attribute
(
'textarea'
,
'name'
,
$this
->
quba
->
get_question_attempt
(
$this
->
slot
)
->
get_qt_field_name
(
'answer'
)),
$this
->
get_does_not_contain_feedback_expectation
());
// Finish the attempt.
$this
->
quba
->
finish_all_questions
();
// Verify.
$this
->
check_current_state
(
question_state
::
$needsgrading
);
$this
->
check_current_mark
(
null
);
$this
->
assertEquals
(
'This is my wonderful essay!'
,
$this
->
quba
->
get_response_summary
(
$this
->
slot
));
// Try to process a an invalid grade.
$this
->
setExpectedException
(
'coding_exception'
);
$this
->
manual_grade
(
'Comment'
,
'10.1'
,
FORMAT_HTML
);
}
}
question/behaviour/rendererbase.php
View file @
5c97a8da
...
...
@@ -68,8 +68,6 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
return
''
;
}
public
function
manual_comment_fields
(
question_attempt
$qa
,
question_display_options
$options
)
{
$inputname
=
$qa
->
get_behaviour_field_name
(
'comment'
);
$id
=
$inputname
.
'_id'
;
...
...
@@ -126,14 +124,8 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
'name'
=>
$markfield
,
'id'
=>
$markfield
);
if
(
!
is_null
(
$currentmark
)
&&
$qa
->
manual_mark_format_is_ok
()
)
{
$attributes
[
'value'
]
=
$qa
->
format_fraction_as_mark
(
$currentmark
/
$maxmark
,
$options
->
markdp
);
}
// We want that the wrong entry is shown.
else
if
(
!
is_null
(
$currentmark
)){
$attributes
[
'value'
]
=
$qa
->
get_submitted_var
(
$qa
->
get_behaviour_field_name
(
'mark'
),
PARAM_RAW_TRIMMED
);
if
(
!
is_null
(
$currentmark
))
{
$attributes
[
'value'
]
=
$currentmark
;
}
$a
=
new
stdClass
();
$a
->
max
=
$qa
->
format_max_mark
(
$options
->
markdp
);
...
...
@@ -153,10 +145,12 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
'value'
=>
$qa
->
get_max_fraction
(),
));
$error
=
$qa
->
get_
validat
ion_message
(
);
$error
=
$qa
->
validat
e_manual_mark
(
$currentmark
);
$errorclass
=
''
;
if
(
$error
!==
''
){
$erroclass
=
'error'
;
if
(
$error
!==
''
)
{
$erroclass
=
' error'
;
$error
=
html_writer
::
tag
(
'span'
,
$error
,
array
(
'class'
=>
'error'
))
.
html_writer
::
empty_tag
(
'br'
);
}
$mark
=
html_writer
::
tag
(
'div'
,
html_writer
::
tag
(
'div'
,
...
...
@@ -172,7 +166,6 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
array
(
'class'
=>
'fcontainer clearfix'
)),
array
(
'class'
=>
'hidden'
));
}
public
function
manual_comment_view
(
question_attempt
$qa
,
question_display_options
$options
)
{
$output
=
''
;
if
(
$qa
->
has_manual_comment
())
{
...
...
question/engine/lib.php
View file @
5c97a8da
...
...
@@ -131,23 +131,6 @@ abstract class question_engine {
$dm
->
set_max_mark_in_attempts
(
$qubaids
,
$slot
,
$newmaxmark
);
}
/**
* MDL-51090
* Validate that the manual grade submitted for a particular question is a
* float.
*
* @param string prefix as constructed in is_manual_grade_in_range.
* @return bool whether the submitted data is a float.
*/
public
static
function
is_manual_grade_float
(
$prefix
)
{
$val
=
optional_param
(
$prefix
.
'-mark'
,
null
,
PARAM_RAW_TRIMMED
);
$mark
=
unformat_float
(
$val
,
true
);
if
(
is_float
(
$mark
))
{
return
true
;
}
return
false
;
}
/**
* Validate that the manual grade submitted for a particular question is in range.
* @param int $qubaid the question_usage id.
...
...
@@ -156,14 +139,12 @@ abstract class question_engine {
*/
public
static
function
is_manual_grade_in_range
(
$qubaid
,
$slot
)
{
$prefix
=
'q'
.
$qubaid
.
':'
.
$slot
.
'_'
;
if
(
!
self
::
is_manual_grade_float
(
$prefix
))
{
return
false
;
}
$mark
=
question_utils
::
optional_param_mark
(
$prefix
.
'-mark'
);
$maxmark
=
optional_param
(
$prefix
.
'-maxmark'
,
null
,
PARAM_FLOAT
);
$minfraction
=
optional_param
(
$prefix
.
':minfraction'
,
null
,
PARAM_FLOAT
);
$maxfraction
=
optional_param
(
$prefix
.
':maxfraction'
,
null
,
PARAM_FLOAT
);
return
is_null
(
$mark
)
||
(
$mark
>=
$minfraction
*
$maxmark
&&
$mark
<=
$maxfraction
*
$maxmark
);
return
$mark
===
''
||
(
$mark
!==
null
&&
$mark
>=
$minfraction
*
$maxmark
&&
$mark
<=
$maxfraction
*
$maxmark
);
}
/**
...
...
@@ -887,8 +868,9 @@ abstract class question_utils {
/**
* Typically, $mark will have come from optional_param($name, null, PARAM_RAW_TRIMMED).
* This method copes with:
* - keeping null or '' input unchanged.
* - nubmers that were typed as either 1.00 or 1,00 form.
* - keeping null or '' input unchanged - important to let teaches set a question back to requries grading.
* - numbers that were typed as either 1.00 or 1,00 form.
* - invalid things, which get turned into null.
*
* @param string|null $mark raw use input of a mark.
* @return float|string|null cleaned mark as a float if possible. Otherwise '' or null.
...
...
@@ -898,7 +880,13 @@ abstract class question_utils {
return
$mark
;
}
return
clean_param
(
str_replace
(
','
,
'.'
,
$mark
),
PARAM_FLOAT
);
$mark
=
str_replace
(
','
,
'.'
,
$mark
);
// This regexp should match the one in validate_param.
if
(
!
preg_match
(
'/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i'
,
$mark
))
{
return
null
;
}
return
clean_param
(
$mark
,
PARAM_FLOAT
);
}
/**
...
...
question/engine/questionattempt.php
View file @
5c97a8da
...
...
@@ -49,10 +49,10 @@ class question_attempt {
const
USE_RAW_DATA
=
'use raw data'
;
/**
* @var string
special value used by manual grading
be
ca
use
{@link PARAM_FLOAT}
*
converts '' to 0.
* @var string
Should not longer
be
use
d.
*
@deprecated since Moodle 3.0
*/
const
PARAM_MARK
=
'parammark'
;
const
PARAM_MARK
=
PARAM_RAW_TRIMMED
;
/**
* @var string special value to indicate a response variable that is uploaded
...
...
@@ -651,13 +651,12 @@ class question_attempt {
* This is used by the manual grading code, particularly in association with
* validation. If there is a mark submitted in the request, then use that,
* otherwise use the latest mark for this question.
* @return number the current mark for this question.
* {@link get_fraction()} * {@link get_max_mark()}.
* @return number the current manual mark for this question, formatted for display.
*/
public
function
get_current_manual_mark
()
{
$mark
=
$this
->
get_submitted_var
(
$this
->
get_behaviour_field_name
(
'mark'
),
question_attempt
::
PARAM_MARK
);
$mark
=
$this
->
get_submitted_var
(
$this
->
get_behaviour_field_name
(
'mark'
),
PARAM_RAW_TRIMMED
);
if
(
is_null
(
$mark
))
{
return
$this
->
get_mark
();
return
format_float
(
$this
->
get_mark
()
,
7
,
true
,
true
)
;
}
else
{
return
$mark
;
}
...
...
@@ -1002,9 +1001,6 @@ class question_attempt {
*/
public
function
get_submitted_var
(
$name
,
$type
,
$postdata
=
null
)
{
switch
(
$type
)
{
case
self
::
PARAM_MARK
:
// Special case to work around PARAM_FLOAT converting '' to 0.
return
question_utils
::
clean_param_mark
(
$this
->
get_submitted_var
(
$name
,
PARAM_RAW_TRIMMED
,
$postdata
));
case
self
::
PARAM_FILES
:
return
$this
->
process_response_files
(
$name
,
$name
,
$postdata
);
...
...
@@ -1026,40 +1022,27 @@ class question_attempt {
}
}
/**
* Validate the manual mark for a question.
* @param unknown $currentmark the user input (e.g. '1,0', '1,0' or 'invalid'.
* @return string any errors with the value, or '' if it is OK.
*/
public
function
validate_manual_mark
(
$currentmark
)
{
if
(
$currentmark
===
null
||
$currentmark
===
''
)
{
return
''
;
}
public
function
get_validation_message
()
{
if
(
!
$this
->
manual_mark_format_is_ok
())
{
$errorclass
=
' error'
;
return
html_writer
::
tag
(
'span'
,
get_string
(
'manualgradeinvalidformat'
,
'question'
),
array
(
'class'
=>
'error'
))
.
html_writer
::
empty_tag
(
'br'
);
$mark
=
question_utils
::
clean_param_mark
(
$currentmark
);
if
(
$mark
===
null
)
{
return
get_string
(
'manualgradeinvalidformat'
,
'question'
);
}
$currentmark
=
$this
->
get_current_manual_mark
();
$maxmark
=
$this
->
get_max_mark
();
if
(
$currentmark
>
$maxmark
*
$this
->
get_max_fraction
()
||
$currentmark
<
$maxmark
*
$this
->
get_min_fraction
())
{
$errorclass
=
' error'
;
return
html_writer
::
tag
(
'span'
,
get_string
(
'manualgradeoutofrange'
,
'question'
),
array
(
'class'
=>
'error'
))
.
html_writer
::
empty_tag
(
'br'
);
if
(
$mark
>
$maxmark
*
$this
->
get_max_fraction
()
||
$mark
<
$maxmark
*
$this
->
get_min_fraction
())
{
return
get_string
(
'manualgradeoutofrange'
,
'question'
);
}
return
''
;
}
/**
* Validates that the manual mark parameter is a float.
* @return bool.
*/
public
function
manual_mark_format_is_ok
()
{
$val
=
$this
->
get_submitted_var
(
$this
->
get_behaviour_field_name
(
'mark'
),
PARAM_RAW_TRIMMED
);
// If it is empy, we get null (it is always the case on start)
if
(
$val
===
null
)
{
return
true
;
}
$mark
=
unformat_float
(
$val
,
true
);
return
is_float
(
$mark
);
}
/**
...
...
question/engine/tests/questionattempt_test.php
View file @
5c97a8da
...
...
@@ -109,36 +109,6 @@ class question_attempt_testcase extends advanced_testcase {
'reallyunlikelyvariablename'
,
PARAM_BOOL
));
}
public
function
test_get_submitted_var_param_mark_not_present
()
{
$this
->
assertNull
(
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
()));
}
public
function
test_get_submitted_var_param_mark_blank
()
{
$this
->
assertSame
(
''
,
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
(
'name'
=>
''
)));
}
public
function
test_get_submitted_var_param_mark_number
()
{
$this
->
assertSame
(
123.0
,
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
(
'name'
=>
'123'
)));
}
public
function
test_get_submitted_var_param_mark_number_uk_decimal
()
{
$this
->
assertSame
(
123.45
,
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
(
'name'
=>
'123.45'
)));
}
public
function
test_get_submitted_var_param_mark_number_eu_decimal
()
{
$this
->
assertSame
(
123.45
,
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
(
'name'
=>
'123,45'
)));
}
public
function
test_get_submitted_var_param_mark_invalid
()
{
$this
->
assertSame
(
0.0
,
$this
->
qa
->
get_submitted_var
(
'name'
,
question_attempt
::
PARAM_MARK
,
array
(
'name'
=>
'frog'
)));
}
public
function
test_get_all_submitted_qt_vars
()
{
$this
->
qa
->
set_usage_id
(
'MDOgzdhS4W'
);
$this
->
qa
->
set_slot
(
1
);
...
...
question/engine/tests/questionattempt_with_steps_test.php
View file @
5c97a8da
...
...
@@ -165,4 +165,20 @@ class question_attempt_with_steps_test extends advanced_testcase {
$this
->
setExpectedException
(
'moodle_exception'
);
$qa
->
get_max_fraction
();
}
public
function
test_validate_manual_mark
()
{
$this
->
qa
->
set_min_fraction
(
0
);
$this
->
qa
->
set_max_fraction
(
1
);
$this
->
assertSame
(
''
,
$this
->
qa
->
validate_manual_mark
(
null
));
$this
->
assertSame
(
''
,
$this
->
qa
->
validate_manual_mark
(
''
));
$this
->
assertSame
(
''
,
$this
->
qa
->
validate_manual_mark
(
'0'
));
$this
->
assertSame
(
''
,
$this
->
qa
->
validate_manual_mark
(
'0.0'
));
$this
->
assertSame
(
''
,
$this
->
qa
->
validate_manual_mark
(
'2,0'
));
$this
->
assertSame
(
get_string
(
'manualgradeinvalidformat'
,
'question'
),
$this
->
qa
->
validate_manual_mark
(
'frog'
));
$this
->
assertSame
(
get_string
(
'manualgradeoutofrange'
,
'question'
),
$this
->
qa
->
validate_manual_mark
(
'2.1'
));
$this
->
assertSame
(
get_string
(
'manualgradeoutofrange'
,
'question'
),
$this
->
qa
->
validate_manual_mark
(
'-0,01'
));
}
}
question/engine/tests/questionutils_test.php
View file @
5c97a8da
...
...
@@ -194,6 +194,7 @@ class question_utils_test extends advanced_testcase {
public
function
test_clean_param_mark
()
{
$this
->
assertNull
(
question_utils
::
clean_param_mark
(
null
));
$this
->
assertNull
(
question_utils
::
clean_param_mark
(
'frog'
));
$this
->
assertSame
(
''
,
question_utils
::
clean_param_mark
(
''
));
$this
->
assertSame
(
0.0
,
question_utils
::
clean_param_mark
(
'0'
));
$this
->
assertSame
(
1.5
,
question_utils
::
clean_param_mark
(
'1.5'
));
...
...
question/engine/upgrade.txt
View file @
5c97a8da
This files describes API changes for the core question system.
=== 3.0, 2.9.2, 2.8.8 ===
1) The extra internal PARAM constant question_attempt::PARAM_MARK should no
longer be used. (It should not have been used outside the core of the
question system). See MDL-51090 if you want more explanation.
=== 2.6 ===
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment