Merge branch 'MDL-61364-master' of git://github.com/lameze/moodle
This commit is contained in:
+26
-47
@@ -1606,61 +1606,40 @@ class context_to_string_translator{
|
||||
/**
|
||||
* Check capability on category
|
||||
*
|
||||
* @param mixed $question object or id
|
||||
* @param string $cap 'add', 'edit', 'view', 'use', 'move'
|
||||
* @param integer $cachecat useful to cache all question records in a category
|
||||
* @param mixed $questionorid object or id. If an object is passed, it should include ->contextid and ->createdby.
|
||||
* @param string $cap 'add', 'edit', 'view', 'use', 'move' or 'tag'.
|
||||
* @param integer $notused no longer used.
|
||||
* @return boolean this user has the capability $cap for this question $question?
|
||||
*/
|
||||
function question_has_capability_on($question, $cap, $cachecat = -1) {
|
||||
global $USER, $DB;
|
||||
function question_has_capability_on($questionorid, $cap, $notused = -1) {
|
||||
global $USER;
|
||||
|
||||
if (is_numeric($questionorid)) {
|
||||
$question = question_bank::load_question_data((int)$questionorid);
|
||||
} else if (is_object($questionorid)) {
|
||||
if (isset($questionorid->contextid) && isset($questionorid->createdby)) {
|
||||
$question = $questionorid;
|
||||
}
|
||||
|
||||
if (!isset($question) && isset($questionorid->id) && $questionorid->id != 0) {
|
||||
$question = question_bank::load_question_data($questionorid->id);
|
||||
}
|
||||
} else {
|
||||
throw new coding_exception('$questionorid parameter needs to be an integer or an object.');
|
||||
}
|
||||
|
||||
$context = context::instance_by_id($question->contextid);
|
||||
|
||||
// these are capabilities on existing questions capabilties are
|
||||
//set per category. Each of these has a mine and all version. Append 'mine' and 'all'
|
||||
$question_questioncaps = array('edit', 'view', 'use', 'move', 'tag');
|
||||
static $questions = array();
|
||||
static $categories = array();
|
||||
static $cachedcat = array();
|
||||
if ($cachecat != -1 && array_search($cachecat, $cachedcat) === false) {
|
||||
$questions += $DB->get_records('question', array('category' => $cachecat), '', 'id,category,createdby');
|
||||
$cachedcat[] = $cachecat;
|
||||
}
|
||||
if (!is_object($question)) {
|
||||
if (!isset($questions[$question])) {
|
||||
if (!$questions[$question] = $DB->get_record('question',
|
||||
array('id' => $question), 'id,category,createdby')) {
|
||||
print_error('questiondoesnotexist', 'question');
|
||||
}
|
||||
}
|
||||
$question = $questions[$question];
|
||||
}
|
||||
if (empty($question->category)) {
|
||||
// This can happen when we have created a fake 'missingtype' question to
|
||||
// take the place of a deleted question.
|
||||
return false;
|
||||
}
|
||||
if (!isset($categories[$question->category])) {
|
||||
if (!$categories[$question->category] = $DB->get_record('question_categories',
|
||||
array('id'=>$question->category))) {
|
||||
print_error('invalidcategory', 'question');
|
||||
}
|
||||
}
|
||||
$category = $categories[$question->category];
|
||||
$context = context::instance_by_id($category->contextid);
|
||||
$capabilitieswithallandmine = ['edit' => 1, 'view' => 1, 'use' => 1, 'move' => 1, 'tag' => 1];
|
||||
|
||||
if (array_search($cap, $question_questioncaps)!== false) {
|
||||
if (!has_capability('moodle/question:' . $cap . 'all', $context)) {
|
||||
if ($question->createdby == $USER->id) {
|
||||
return has_capability('moodle/question:' . $cap . 'mine', $context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!isset($capabilitieswithallandmine[$cap])) {
|
||||
return has_capability('moodle/question:' . $cap, $context);
|
||||
} else {
|
||||
return has_capability('moodle/question:' . $cap . 'all', $context) ||
|
||||
($question->createdby == $USER->id && has_capability('moodle/question:' . $cap . 'mine', $context));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1498,4 +1498,469 @@ class core_questionlib_testcase extends advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for tests of question_has_capability_on_context and question_require_capability_on_context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function question_capability_on_question_provider() {
|
||||
return [
|
||||
'Unrelated capability which is present' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:config' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => true,
|
||||
'expect' => true,
|
||||
],
|
||||
'Unrelated capability which is present (not owner)' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:config' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => false,
|
||||
'expect' => true,
|
||||
],
|
||||
'Unrelated capability which is not set' => [
|
||||
'capabilities' => [
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => true,
|
||||
'expect' => false,
|
||||
],
|
||||
'Unrelated capability which is not set (not owner)' => [
|
||||
'capabilities' => [
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => false,
|
||||
'expect' => false,
|
||||
],
|
||||
'Unrelated capability which is prevented' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:config' => CAP_PREVENT,
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => true,
|
||||
'expect' => false,
|
||||
],
|
||||
'Unrelated capability which is prevented (not owner)' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:config' => CAP_PREVENT,
|
||||
],
|
||||
'testcapability' => 'config',
|
||||
'isowner' => false,
|
||||
'expect' => false,
|
||||
],
|
||||
'Related capability which is not set' => [
|
||||
'capabilities' => [
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => true,
|
||||
'expect' => false,
|
||||
],
|
||||
'Related capability which is not set (not owner)' => [
|
||||
'capabilities' => [
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => false,
|
||||
'expect' => false,
|
||||
],
|
||||
'Related capability which is allowed at all, unset at mine' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => true,
|
||||
'expect' => true,
|
||||
],
|
||||
'Related capability which is allowed at all, unset at mine (not owner)' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => false,
|
||||
'expect' => true,
|
||||
],
|
||||
'Related capability which is allowed at all, prevented at mine' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_ALLOW,
|
||||
'moodle/question:editmine' => CAP_PREVENT,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => true,
|
||||
'expect' => true,
|
||||
],
|
||||
'Related capability which is allowed at all, prevented at mine (not owner)' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_ALLOW,
|
||||
'moodle/question:editmine' => CAP_PREVENT,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => false,
|
||||
'expect' => true,
|
||||
],
|
||||
'Related capability which is unset all, allowed at mine' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_PREVENT,
|
||||
'moodle/question:editmine' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => true,
|
||||
'expect' => true,
|
||||
],
|
||||
'Related capability which is unset all, allowed at mine (not owner)' => [
|
||||
'capabilities' => [
|
||||
'moodle/question:editall' => CAP_PREVENT,
|
||||
'moodle/question:editmine' => CAP_ALLOW,
|
||||
],
|
||||
'testcapability' => 'edit',
|
||||
'isowner' => false,
|
||||
'expect' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when passing a stdClass.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $expectall The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_stdClass($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$otheruser = $this->getDataGenerator()->create_user();
|
||||
$roleid = $this->getDataGenerator()->create_role();
|
||||
$category = $this->getDataGenerator()->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
|
||||
// Assign the user to the role.
|
||||
role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
// Assign the capabilities to the role.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $context->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
// The current fake question we make use of is always a stdClass and typically has no ID.
|
||||
$fakequestion = (object) [
|
||||
'contextid' => $context->id,
|
||||
];
|
||||
|
||||
if ($isowner) {
|
||||
$fakequestion->createdby = $user->id;
|
||||
} else {
|
||||
$fakequestion->createdby = $otheruser->id;
|
||||
}
|
||||
|
||||
$result = question_has_capability_on($fakequestion, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when using a real question.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $expectall The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_question_definition($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
$otheruser = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
// Assign the user to the role.
|
||||
role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
// Assign the capabilities to the role.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $context->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
|
||||
// Create the question.
|
||||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when using a real question.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $expectall The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_question_id($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
$otheruser = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
// Assign the user to the role.
|
||||
role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
// Assign the capabilities to the role.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $context->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
|
||||
// Create the question.
|
||||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question->id, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when using a real question.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $expectall The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_question_string_id($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
$otheruser = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
// Assign the user to the role.
|
||||
role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
// Assign the capabilities to the role.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $context->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
|
||||
// Create the question.
|
||||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on((string) $question->id, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when using a real question.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $expectall The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_moved_question($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
$otheruser = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
$newcategory = $generator->create_category();
|
||||
$newcontext = context_coursecat::instance($newcategory->id);
|
||||
$newquestioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $newcontext->id,
|
||||
]);
|
||||
|
||||
// Assign the user to the role in the _new_ context..
|
||||
role_assign($roleid, $user->id, $newcontext->id);
|
||||
|
||||
// Assign the capabilities to the role in the _new_ context.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $newcontext->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
$newcontext->mark_dirty();
|
||||
|
||||
// Create the question.
|
||||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
// Move the question.
|
||||
question_move_questions_to_category([$question->id], $newquestioncat->id);
|
||||
|
||||
// Test that the capability is correct after the question has been moved.
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question->id, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the deprecated question_has_capability_on function when using a real question.
|
||||
*
|
||||
* @dataProvider question_capability_on_question_provider
|
||||
* @param array $capabilities The capability assignments to set.
|
||||
* @param string $capability The capability to test
|
||||
* @param bool $isowner The expectation when passing false to checkmine.
|
||||
* @param bool $expectmine The expectation when passing true to checkmine.
|
||||
*/
|
||||
public function test_question_has_capability_on_using_question($capabilities, $capability, $isowner, $expect) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
$otheruser = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
// Assign the user to the role.
|
||||
role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
// Assign the capabilities to the role.
|
||||
foreach ($capabilities as $capname => $capvalue) {
|
||||
assign_capability($capname, $capvalue, $roleid, $context->id);
|
||||
}
|
||||
$context->mark_dirty();
|
||||
|
||||
// Create the question.
|
||||
$question = $questiongenerator->create_question('truefalse', null, [
|
||||
'category' => $questioncat->id,
|
||||
]);
|
||||
$question = question_bank::load_question_data($question->id);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that question_has_capability_on throws an exception for wrong parameter types.
|
||||
*/
|
||||
public function test_question_has_capability_on_wrong_param_type() {
|
||||
// Create the test data.
|
||||
$generator = $this->getDataGenerator();
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
$user = $generator->create_user();
|
||||
|
||||
$category = $generator->create_category();
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncat = $questiongenerator->create_question_category([
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
|
||||
// Create the question.
|
||||
$question = $questiongenerator->create_question('truefalse', null, [
|
||||
'category' => $questioncat->id,
|
||||
]);
|
||||
$question = question_bank::load_question_data($question->id);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = $user->id;
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on((string)$question->id, 'tag');
|
||||
$this->assertFalse($result);
|
||||
|
||||
$this->expectException('coding_exception');
|
||||
$this->expectExceptionMessage('$questionorid parameter needs to be an integer or an object.');
|
||||
question_has_capability_on('one', 'tag');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,12 +1333,12 @@ function quiz_question_edit_button($cmid, $question, $returnurl, $contentafteric
|
||||
// What sort of icon should we show?
|
||||
$action = '';
|
||||
if (!empty($question->id) &&
|
||||
(question_has_capability_on($question, 'edit', $question->category) ||
|
||||
question_has_capability_on($question, 'move', $question->category))) {
|
||||
(question_has_capability_on($question, 'edit') ||
|
||||
question_has_capability_on($question, 'move'))) {
|
||||
$action = $stredit;
|
||||
$icon = 't/edit';
|
||||
} else if (!empty($question->id) &&
|
||||
question_has_capability_on($question, 'view', $question->category)) {
|
||||
question_has_capability_on($question, 'view')) {
|
||||
$action = $strview;
|
||||
$icon = 'i/info';
|
||||
}
|
||||
@@ -1390,7 +1390,7 @@ function quiz_question_preview_url($quiz, $question, $variant = null) {
|
||||
*/
|
||||
function quiz_question_preview_button($quiz, $question, $label = false, $variant = null) {
|
||||
global $PAGE;
|
||||
if (!question_has_capability_on($question, 'use', $question->category)) {
|
||||
if (!question_has_capability_on($question, 'use')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","core_question/repository","core_question/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){return c.get_string("questiontags","question").then(function(b){return a.setTitle(b),b}).fail(f.exception),a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit(),b.preventDefault()}),a.getRoot().on("submit","form",function(b){p(a,h).then(function(){a.hide()}).fail(f.exception),b.preventDefault(),b.stopPropagation()}),a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("cantag"),o=d.data("contextid");l.then(function(a){k(h),m(h);var c={id:e},d=b.loadFragment("question","tags_form",o,c);return a.setBody(d),d.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception),g?a.getRoot().find(i.actions.save).show():a.getRoot().find(i.actions.save).hide(),a}).fail(f.exception),c.preventDefault()})},p=function(a,b){k(b),m(b);var c=l(a);return h.submitTagCreateUpdateForm(c).always(function(){n(b),j(b)}).fail(f.exception)};return{init:function(b){b=a(b),o(b)}}});
|
||||
define(["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","core_question/repository","core_question/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(a,b){a.getBody().attr("data-contextid",b)},p=function(a){return a.getBody().data("contextid")},q=function(a,b){a.getBody().attr("data-questionid",b)},r=function(a){return a.getBody().data("questionid")},s=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){return c.get_string("questiontags","question").then(function(b){return a.setTitle(b),b}).fail(f.exception),a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit(),b.preventDefault()}),a.getRoot().on("submit","form",function(b){t(a,h).then(function(){a.hide()}).fail(f.exception),b.preventDefault(),b.stopPropagation()}),a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("cantag"),p=d.data("contextid");l.then(function(a){k(h),m(h);var c={id:e},d=b.loadFragment("question","tags_form",p,c);return a.setBody(d),d.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception),g?a.getRoot().find(i.actions.save).show():a.getRoot().find(i.actions.save).hide(),q(a,e),o(a,p),a}).fail(f.exception),c.preventDefault()})},t=function(a,b){k(b),m(b);var c=l(a),d=r(a),e=p(a);return h.submitTagCreateUpdateForm(d,e,c).always(function(){n(b),j(b)}).fail(f.exception)};return{init:function(b){b=a(b),s(b)}}});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_question_submit_tags_form",args:{formdata:a}};return b.call([c])[0]};return{submitTagCreateUpdateForm:c}});
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c,d){var e={methodname:"core_question_submit_tags_form",args:{questionid:a,contextid:c,formdata:d}};return b.call([e])[0]};return{submitTagCreateUpdateForm:c}});
|
||||
@@ -98,6 +98,46 @@ define([
|
||||
loadingIconContainer.addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the context Id data attribute on the modal.
|
||||
*
|
||||
* @param {Promise} modal The modal promise.
|
||||
* @param {int} contextId The context id.
|
||||
*/
|
||||
var setContextId = function(modal, contextId) {
|
||||
modal.getBody().attr('data-contextid', contextId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the context Id data attribute value from the modal body.
|
||||
*
|
||||
* @param {Promise} modal The modal promise.
|
||||
* @return {int} The context id.
|
||||
*/
|
||||
var getContextId = function(modal) {
|
||||
return modal.getBody().data('contextid');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the question Id data attribute on the modal.
|
||||
*
|
||||
* @param {Promise} modal The modal promise.
|
||||
* @param {int} questionId The question Id.
|
||||
*/
|
||||
var setQuestionId = function(modal, questionId) {
|
||||
modal.getBody().attr('data-questionid', questionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the question Id data attribute value from the modal body.
|
||||
*
|
||||
* @param {Promise} modal The modal promise.
|
||||
* @return {int} The question Id.
|
||||
*/
|
||||
var getQuestionId = function(modal) {
|
||||
return modal.getBody().data('questionid');
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners for the module.
|
||||
*
|
||||
@@ -186,6 +226,9 @@ define([
|
||||
modal.getRoot().find(QuestionSelectors.actions.save).hide();
|
||||
}
|
||||
|
||||
setQuestionId(modal, questionId);
|
||||
setContextId(modal, contextId);
|
||||
|
||||
return modal;
|
||||
}).fail(Notification.exception);
|
||||
|
||||
@@ -207,9 +250,11 @@ define([
|
||||
startLoading(root);
|
||||
|
||||
var formData = getFormData(modal);
|
||||
var questionId = getQuestionId(modal);
|
||||
var contextId = getContextId(modal);
|
||||
|
||||
// Send the form data to the server for processing.
|
||||
return Repository.submitTagCreateUpdateForm(formData)
|
||||
return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)
|
||||
.always(function() {
|
||||
// Regardless of success or error we should always stop
|
||||
// the loading icon and re-enable the buttons.
|
||||
|
||||
@@ -31,10 +31,12 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
||||
* @param {string} formdata The URL encoded values from the form
|
||||
* @return {promise}
|
||||
*/
|
||||
var submitTagCreateUpdateForm = function(formdata) {
|
||||
var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {
|
||||
var request = {
|
||||
methodname: 'core_question_submit_tags_form',
|
||||
args: {
|
||||
questionid: questionId,
|
||||
contextid: contextId,
|
||||
formdata: formdata
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,10 +56,11 @@ class tags_action_column extends action_column_base {
|
||||
question_has_capability_on($question, 'view')) {
|
||||
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
$category = $DB->get_record('question_categories', ['id' => $question->category], 'contextid');
|
||||
$url = $this->qbank->edit_question_url($question->id);
|
||||
$qbank = $this->qbank;
|
||||
$url = $qbank->edit_question_url($question->id);
|
||||
$editingcontext = $qbank->get_most_specific_context();
|
||||
|
||||
$this->print_tag_icon($question->id, $url, $cantag, $category->contextid);
|
||||
$this->print_tag_icon($question->id, $url, $cantag, $editingcontext->id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -929,7 +929,7 @@ class view {
|
||||
if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
|
||||
$key = $matches[1];
|
||||
$questionlist .= $key.',';
|
||||
question_require_capability_on($key, 'edit');
|
||||
question_require_capability_on((int)$key, 'edit');
|
||||
if (questions_in_use(array($key))) {
|
||||
$questionnames .= '* ';
|
||||
$inuse = true;
|
||||
|
||||
@@ -122,6 +122,8 @@ class core_question_external extends external_api {
|
||||
*/
|
||||
public static function submit_tags_form_parameters() {
|
||||
return new external_function_parameters([
|
||||
'questionid' => new external_value(PARAM_INT, 'The question id'),
|
||||
'contextid' => new external_value(PARAM_INT, 'The editing context id'),
|
||||
'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'),
|
||||
]);
|
||||
}
|
||||
@@ -129,51 +131,67 @@ class core_question_external extends external_api {
|
||||
/**
|
||||
* Handles the tags form submission.
|
||||
*
|
||||
* @param int $questionid The question id.
|
||||
* @param int $contextid The editing context id.
|
||||
* @param string $formdata The question tag form data in a URI encoded param string
|
||||
* @return array The created or modified question tag
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function submit_tags_form($formdata) {
|
||||
global $USER, $DB, $CFG;
|
||||
public static function submit_tags_form($questionid, $contextid, $formdata) {
|
||||
global $DB, $CFG;
|
||||
|
||||
$data = [];
|
||||
$result = ['status' => false];
|
||||
|
||||
// Parameter validation.
|
||||
$params = self::validate_parameters(self::submit_tags_form_parameters(), ['formdata' => $formdata]);
|
||||
$context = \context_user::instance($USER->id);
|
||||
$params = self::validate_parameters(self::submit_tags_form_parameters(), [
|
||||
'questionid' => $questionid,
|
||||
'contextid' => $contextid,
|
||||
'formdata' => $formdata
|
||||
]);
|
||||
|
||||
self::validate_context($context);
|
||||
$editingcontext = \context::instance_by_id($contextid);
|
||||
self::validate_context($editingcontext);
|
||||
parse_str($params['formdata'], $data);
|
||||
|
||||
if (!empty($data['id'])) {
|
||||
$questionid = clean_param($data['id'], PARAM_INT);
|
||||
$question = $DB->get_record('question', array('id' => $questionid));
|
||||
if (!$question = $DB->get_record_sql('
|
||||
SELECT q.*, qc.contextid
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON qc.id = q.category
|
||||
WHERE q.id = ?', [$questionid])) {
|
||||
print_error('questiondoesnotexist', 'question');
|
||||
}
|
||||
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
require_once($CFG->dirroot . '/question/type/tags_form.php');
|
||||
|
||||
require_once($CFG->dirroot . '/question/type/tags_form.php');
|
||||
$mform = new \core_question\form\tags(null, null, 'post', '', null, $cantag, $data);
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
$questioncontext = \context::instance_by_id($question->contextid);
|
||||
$formoptions = [
|
||||
'editingcontext' => $editingcontext,
|
||||
'questioncontext' => $questioncontext
|
||||
];
|
||||
|
||||
if ($validateddata = $mform->get_data()) {
|
||||
// Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
|
||||
// The only way to discover is checking if the tag element is an array.
|
||||
if ($cantag) {
|
||||
if (is_array($validateddata->tags)) {
|
||||
$categorycontext = context::instance_by_id($validateddata->contextid);
|
||||
$mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data);
|
||||
|
||||
core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
|
||||
$categorycontext, $validateddata->tags);
|
||||
if ($validateddata = $mform->get_data()) {
|
||||
if ($cantag) {
|
||||
if (isset($validateddata->tags)) {
|
||||
// Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
|
||||
// The only way to discover is checking if the tag element is an array.
|
||||
$tags = is_array($validateddata->tags) ? $validateddata->tags : [];
|
||||
|
||||
$result['status'] = true;
|
||||
} else {
|
||||
// If the tags element is not array, this means we don't have any tags to be set.
|
||||
// This is the only way to assume the user removed all tags from the question.
|
||||
core_tag_tag::remove_all_item_tags('core_question', 'question', $validateddata->id);
|
||||
core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
|
||||
$questioncontext, $tags);
|
||||
|
||||
$result['status'] = true;
|
||||
}
|
||||
$result['status'] = true;
|
||||
}
|
||||
|
||||
if (isset($validateddata->coursetags)) {
|
||||
$coursetags = is_array($validateddata->coursetags) ? $validateddata->coursetags : [];
|
||||
core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
|
||||
$editingcontext->get_course_context(false), $coursetags);
|
||||
|
||||
$result['status'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ abstract class question_test_helper {
|
||||
$catcontext = context::instance_by_id($cat->contextid, MUST_EXIST);
|
||||
$contexts = new question_edit_contexts($catcontext);
|
||||
$dataforformconstructor = new stdClass();
|
||||
$dataforformconstructor->createdby = $questiondata->createdby;
|
||||
$dataforformconstructor->qtype = $questiondata->qtype;
|
||||
$dataforformconstructor->contextid = $questiondata->contextid = $catcontext->id;
|
||||
$dataforformconstructor->category = $questiondata->category = $cat->id;
|
||||
|
||||
+1
-1
@@ -857,7 +857,7 @@ class qformat_default {
|
||||
// export the question displaying message
|
||||
$count++;
|
||||
|
||||
if (question_has_capability_on($question, 'view', $question->category)) {
|
||||
if (question_has_capability_on($question, 'view')) {
|
||||
$expout .= $this->writequestion($question, $contextid) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
+30
-14
@@ -41,26 +41,42 @@ function core_question_output_fragment_tags_form($args) {
|
||||
require_once($CFG->dirroot . '/question/type/tags_form.php');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
$id = clean_param($args['id'], PARAM_INT);
|
||||
$editingcontext = $args['context'];
|
||||
|
||||
$question = $DB->get_record('question', ['id' => $id]);
|
||||
$category = $DB->get_record('question_categories', array('id' => $question->category));
|
||||
$context = \context::instance_by_id($category->contextid);
|
||||
|
||||
$toform = new stdClass();
|
||||
$toform->id = $question->id;
|
||||
$toform->questioncategory = $category->name;
|
||||
$toform->questionname = $question->name;
|
||||
$toform->categoryid = $category->id;
|
||||
$toform->contextid = $category->contextid;
|
||||
$toform->context = $context->get_context_name();
|
||||
|
||||
if (core_tag_tag::is_enabled('core_question', 'question')) {
|
||||
$toform->tags = core_tag_tag::get_item_tags_array('core_question', 'question', $question->id);
|
||||
if ($coursecontext = $editingcontext->get_course_context(false)) {
|
||||
$course = $DB->get_record('course', ['id' => $coursecontext->instanceid]);
|
||||
$filtercourses = [$course];
|
||||
} else {
|
||||
$filtercourses = null;
|
||||
}
|
||||
|
||||
// Load the question tags and filter the course tags by the current
|
||||
// course.
|
||||
get_question_options($question, true, $filtercourses);
|
||||
|
||||
$category = $question->categoryobject;
|
||||
$questioncontext = \context::instance_by_id($category->contextid);
|
||||
|
||||
$formoptions = [
|
||||
'editingcontext' => $editingcontext,
|
||||
'questioncontext' => $questioncontext
|
||||
];
|
||||
$data = [
|
||||
'id' => $question->id,
|
||||
'questioncategory' => $category->name,
|
||||
'questionname' => $question->name,
|
||||
'categoryid' => $category->id,
|
||||
'contextid' => $category->contextid,
|
||||
'context' => $questioncontext->get_context_name(),
|
||||
'tags' => isset($question->tags) ? $question->tags : [],
|
||||
'coursetags' => isset($question->coursetags) ? $question->coursetags : [],
|
||||
];
|
||||
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
$mform = new \core_question\form\tags(null, null, 'post', '', null, $cantag, $toform);
|
||||
$mform->set_data($toform);
|
||||
$mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data);
|
||||
$mform->set_data($data);
|
||||
|
||||
return $mform->render();
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ if (isset($question->categoryobject)) {
|
||||
$question->formoptions = new stdClass();
|
||||
|
||||
$categorycontext = context::instance_by_id($category->contextid);
|
||||
$question->contextid = $category->contextid;
|
||||
$addpermission = has_capability('moodle/question:add', $categorycontext);
|
||||
|
||||
if ($id) {
|
||||
|
||||
@@ -102,4 +102,374 @@ class core_question_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals('errorsavingflags', $e->errorcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_tags_form should throw an exception when the question id doesn't match
|
||||
* a question.
|
||||
*/
|
||||
public function test_submit_tags_form_incorrect_question_id() {
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
$editingcontext = $questioncontext;
|
||||
$question = $questions[0];
|
||||
// Generate an id for a question that doesn't exist.
|
||||
$missingquestionid = $questions[1]->id * 2;
|
||||
$question->id = $missingquestionid;
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
|
||||
|
||||
// We should receive an exception if the question doesn't exist.
|
||||
$this->expectException('moodle_exception');
|
||||
core_question_external::submit_tags_form($missingquestionid, $editingcontext->id, $formdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_tags_form should throw an exception when the context id doesn't match
|
||||
* a context.
|
||||
*/
|
||||
public function test_submit_tags_form_incorrect_context_id() {
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
$editingcontext = $questioncontext;
|
||||
$question = $questions[0];
|
||||
// Generate an id for a context that doesn't exist.
|
||||
$missingcontextid = $editingcontext->id * 200;
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
|
||||
|
||||
// We should receive an exception if the question doesn't exist.
|
||||
$this->expectException('moodle_exception');
|
||||
core_question_external::submit_tags_form($question->id, $missingcontextid, $formdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_tags_form should return false when tags are disabled.
|
||||
*/
|
||||
public function test_submit_tags_form_tags_disabled() {
|
||||
global $CFG;
|
||||
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
$editingcontext = $questioncontext;
|
||||
$question = $questions[0];
|
||||
$user = $this->create_user_can_tag($course);
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
|
||||
|
||||
$this->setUser($user);
|
||||
$CFG->usetags = false;
|
||||
$result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
|
||||
$CFG->usetags = true;
|
||||
|
||||
$this->assertFalse($result['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_tags_form should return false if the user does not have any capability
|
||||
* to tag the question.
|
||||
*/
|
||||
public function test_submit_tags_form_no_tag_permissions() {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
$editingcontext = $questioncontext;
|
||||
$question = $questions[0];
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string(
|
||||
$question,
|
||||
$qcat,
|
||||
$questioncontext,
|
||||
['foo'],
|
||||
['bar']
|
||||
);
|
||||
|
||||
// Prohibit all of the tag capabilities.
|
||||
assign_capability('moodle/question:tagmine', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
|
||||
assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
|
||||
$user->ignoresesskey = true;
|
||||
$this->setUser($user);
|
||||
|
||||
$result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
|
||||
|
||||
$this->assertFalse($result['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_tags_form should return false if the user only has the capability to
|
||||
* tag their own questions and the question is not theirs.
|
||||
*/
|
||||
public function test_submit_tags_form_tagmine_permission_non_owner_question() {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
|
||||
$questiongenerator = $generator->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
$editingcontext = $questioncontext;
|
||||
$question = $questions[0];
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string(
|
||||
$question,
|
||||
$qcat,
|
||||
$questioncontext,
|
||||
['foo'],
|
||||
['bar']
|
||||
);
|
||||
|
||||
// Make sure the question isn't created by the user.
|
||||
$question->createdby = $user->id + 1;
|
||||
|
||||
// Prohibit all of the tag capabilities.
|
||||
assign_capability('moodle/question:tagmine', CAP_ALLOW, $teacherrole->id, $questioncontext->id);
|
||||
assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
|
||||
$user->ignoresesskey = true;
|
||||
$this->setUser($user);
|
||||
|
||||
$result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
|
||||
|
||||
$this->assertFalse($result['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provided for the submit_tags_form test to check that course tags are
|
||||
* only created in the correct editing and question context combinations.
|
||||
*
|
||||
* @return array Test cases
|
||||
*/
|
||||
public function get_submit_tags_form_testcases() {
|
||||
return [
|
||||
'course - course' => [
|
||||
'editingcontext' => 'course',
|
||||
'questioncontext' => 'course',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'course - course - empty tags' => [
|
||||
'editingcontext' => 'course',
|
||||
'questioncontext' => 'course',
|
||||
'questiontags' => [],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'course - course category' => [
|
||||
'editingcontext' => 'course',
|
||||
'questioncontext' => 'category',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => true
|
||||
],
|
||||
'course - system' => [
|
||||
'editingcontext' => 'course',
|
||||
'questioncontext' => 'system',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => true
|
||||
],
|
||||
'course category - course' => [
|
||||
'editingcontext' => 'category',
|
||||
'questioncontext' => 'course',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'course category - course category' => [
|
||||
'editingcontext' => 'category',
|
||||
'questioncontext' => 'category',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'course category - system' => [
|
||||
'editingcontext' => 'category',
|
||||
'questioncontext' => 'system',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'system - course' => [
|
||||
'editingcontext' => 'system',
|
||||
'questioncontext' => 'course',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'system - course category' => [
|
||||
'editingcontext' => 'system',
|
||||
'questioncontext' => 'category',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
'system - system' => [
|
||||
'editingcontext' => 'system',
|
||||
'questioncontext' => 'system',
|
||||
'questiontags' => ['foo'],
|
||||
'coursetags' => ['bar'],
|
||||
'expectcoursetags' => false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that submit_tags_form only creates course tags when the correct combination
|
||||
* of editing context and question context is provided.
|
||||
*
|
||||
* Course tags can only be set on a course category or system context question that
|
||||
* is being editing in a course context.
|
||||
*
|
||||
* @dataProvider get_submit_tags_form_testcases()
|
||||
* @param string $editingcontext The type of the context the question is being edited in
|
||||
* @param string $questioncontext The type of the context the question belongs to
|
||||
* @param string[] $questiontags The tag names to set as question tags
|
||||
* @param string[] $coursetags The tag names to set as course tags
|
||||
* @param bool $expectcoursetags If the given course tags should have been set or not
|
||||
*/
|
||||
public function test_submit_tags_form_context_combinations(
|
||||
$editingcontext,
|
||||
$questioncontext,
|
||||
$questiontags,
|
||||
$coursetags,
|
||||
$expectcoursetags
|
||||
) {
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions($questioncontext);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$questioncontext = context::instance_by_id($qcat->contextid);
|
||||
|
||||
switch($editingcontext) {
|
||||
case 'system':
|
||||
$editingcontext = context_system::instance();
|
||||
break;
|
||||
|
||||
case 'category':
|
||||
$editingcontext = context_coursecat::instance($category->id);
|
||||
break;
|
||||
|
||||
default:
|
||||
$editingcontext = context_course::instance($course->id);
|
||||
}
|
||||
|
||||
$user = $this->create_user_can_tag($course);
|
||||
$question = $questions[0];
|
||||
$formdata = $this->generate_encoded_submit_tags_form_string(
|
||||
$question,
|
||||
$qcat,
|
||||
$questioncontext,
|
||||
$questiontags, // Question tags.
|
||||
$coursetags // Course tags.
|
||||
);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
$result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
|
||||
|
||||
$this->assertTrue($result['status']);
|
||||
|
||||
$tagobjects = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
|
||||
$coursetagobjects = [];
|
||||
$questiontagobjects = [];
|
||||
|
||||
if ($expectcoursetags) {
|
||||
// If the use case is expecting course tags to be created then split
|
||||
// the tags into course tags and question tags and ensure we have
|
||||
// the correct number of course tags.
|
||||
|
||||
while ($tagobject = array_shift($tagobjects)) {
|
||||
if ($tagobject->taginstancecontextid == $questioncontext->id) {
|
||||
$questiontagobjects[] = $tagobject;
|
||||
} else if ($tagobject->taginstancecontextid == $coursecontext->id) {
|
||||
$coursetagobjects[] = $tagobject;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(count($coursetags), $coursetagobjects);
|
||||
} else {
|
||||
$questiontagobjects = $tagobjects;
|
||||
}
|
||||
|
||||
// Ensure the expected number of question tags was created.
|
||||
$this->assertCount(count($questiontags), $questiontagobjects);
|
||||
|
||||
foreach ($questiontagobjects as $tagobject) {
|
||||
// If we have any question tags then make sure they are in the list
|
||||
// of expected tags and have the correct context.
|
||||
$this->assertContains($tagobject->name, $questiontags);
|
||||
$this->assertEquals($questioncontext->id, $tagobject->taginstancecontextid);
|
||||
}
|
||||
|
||||
foreach ($coursetagobjects as $tagobject) {
|
||||
// If we have any course tags then make sure they are in the list
|
||||
// of expected course tags and have the correct context.
|
||||
$this->assertContains($tagobject->name, $coursetags);
|
||||
$this->assertEquals($coursecontext->id, $tagobject->taginstancecontextid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the encoded form data expected by the submit_tags_form external function.
|
||||
*
|
||||
* @param stdClass $question The question record
|
||||
* @param stdClass $questioncategory The question category record
|
||||
* @param context $questioncontext Context for the question category
|
||||
* @param array $tags A list of tag names for the question
|
||||
* @param array $coursetags A list of course tag names for the question
|
||||
* @return string HTML encoded string of the data
|
||||
*/
|
||||
protected function generate_encoded_submit_tags_form_string($question, $questioncategory,
|
||||
$questioncontext, $tags = [], $coursetags = []) {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/question/type/tags_form.php');
|
||||
|
||||
$data = [
|
||||
'id' => $question->id,
|
||||
'categoryid' => $questioncategory->id,
|
||||
'contextid' => $questioncontext->id,
|
||||
'questionname' => $question->name,
|
||||
'questioncategory' => $questioncategory->name,
|
||||
'context' => $questioncontext->get_context_name(false),
|
||||
'tags' => $tags,
|
||||
'coursetags' => $coursetags
|
||||
];
|
||||
$data = core_question\form\tags::mock_generate_submit_keys($data);
|
||||
|
||||
return http_build_query($data, '', '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user, enrol them in the course, and give them the capability to
|
||||
* tag all questions in the system context.
|
||||
*
|
||||
* @param stdClass $course The course record to enrol in
|
||||
* @return stdClass The user record
|
||||
*/
|
||||
protected function create_user_can_tag($course) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$roleid = $generator->create_role();
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
|
||||
$systemcontext = context_system::instance();
|
||||
|
||||
$generator->role_assign($roleid, $user->id, $systemcontext->id);
|
||||
$generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
|
||||
|
||||
// Give the user global ability to tag questions.
|
||||
assign_capability('moodle/question:tagall', CAP_ALLOW, $roleid, $systemcontext, true);
|
||||
// Allow the user to submit form data.
|
||||
$user->ignoresesskey = true;
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,4 +89,43 @@ class core_question_generator extends component_generator_base {
|
||||
$question->createdby = 0;
|
||||
return question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a course category, course, a question category, and 2 questions
|
||||
* for testing.
|
||||
*
|
||||
* @param string $type The type of question category to create.
|
||||
* @return array The created data objects
|
||||
*/
|
||||
public function setup_course_and_questions($type = 'course') {
|
||||
$datagenerator = $this->datagenerator;
|
||||
$category = $datagenerator->create_category();
|
||||
$course = $datagenerator->create_course([
|
||||
'numsections' => 5,
|
||||
'category' => $category->id
|
||||
]);
|
||||
|
||||
switch ($type) {
|
||||
case 'category':
|
||||
$context = context_coursecat::instance($category->id);
|
||||
break;
|
||||
|
||||
case 'system':
|
||||
$context = context_system::instance();
|
||||
break;
|
||||
|
||||
default:
|
||||
$context = context_course::instance($course->id);
|
||||
break;
|
||||
}
|
||||
|
||||
$qcat = $this->create_question_category(['contextid' => $context->id]);
|
||||
|
||||
$questions = array(
|
||||
$this->create_question('shortanswer', null, ['category' => $qcat->id]),
|
||||
$this->create_question('shortanswer', null, ['category' => $qcat->id]),
|
||||
);
|
||||
|
||||
return array($category, $course, $qcat, $questions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,11 @@ class qtype_calculatedsimple_test_helper extends question_test_helper {
|
||||
}
|
||||
|
||||
public function get_calculatedsimple_question_data_sumwithvariants() {
|
||||
global $USER;
|
||||
$q = new stdClass();
|
||||
|
||||
$q->name = 'Calculated simple';
|
||||
$q->createdby = $USER->id;
|
||||
$q->questiontext = '<p>This is a simple sum of two variables.</p>';
|
||||
$q->questiontextformat = '1';
|
||||
$q->generalfeedback = '<p>The answer is {a} + {b}</p>';
|
||||
|
||||
@@ -43,6 +43,8 @@ class qtype_gapselect_edit_form_base_testable extends qtype_gapselect_edit_form_
|
||||
$category = question_make_default_categories(array($syscontext));
|
||||
$fakequestion = new stdClass();
|
||||
$fakequestion->qtype = 'stack';
|
||||
$fakequestion->contextid = $syscontext->id;
|
||||
$fakequestion->createdby = 2;
|
||||
$fakequestion->category = $category->id;
|
||||
$fakequestion->questiontext = 'Test [[1]] question [[2]]';
|
||||
$fakequestion->options = new stdClass();
|
||||
|
||||
@@ -41,6 +41,8 @@ class tags extends \moodleform {
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$customdata = $this->_customdata;
|
||||
|
||||
$mform->disable_form_change_checker();
|
||||
|
||||
$mform->addElement('hidden', 'id');
|
||||
@@ -56,7 +58,29 @@ class tags extends \moodleform {
|
||||
$mform->addElement('static', 'questioncategory', get_string('categorycurrent', 'question'));
|
||||
$mform->addElement('static', 'context', '');
|
||||
|
||||
$mform->addElement('tags', 'tags', get_string('tags'),
|
||||
['itemtype' => 'question', 'component' => 'core_question']);
|
||||
if (\core_tag_tag::is_enabled('core_question', 'question')) {
|
||||
$mform->addElement('tags', 'tags', get_string('tags'),
|
||||
['itemtype' => 'question', 'component' => 'core_question']);
|
||||
|
||||
// Is the question category in a course context?
|
||||
$qcontext = $customdata['questioncontext'];
|
||||
$qcoursecontext = $qcontext->get_course_context(false);
|
||||
$iscourseoractivityquestion = !empty($qcoursecontext);
|
||||
// Is the current context we're editing in a course context?
|
||||
$editingcontext = $customdata['editingcontext'];
|
||||
$editingcoursecontext = $editingcontext->get_course_context(false);
|
||||
$iseditingcontextcourseoractivity = !empty($editingcoursecontext);
|
||||
|
||||
if ($iseditingcontextcourseoractivity && !$iscourseoractivityquestion) {
|
||||
// If the question is being edited in a course or activity context
|
||||
// and the question isn't a course or activity level question then
|
||||
// allow course tags to be added to the course.
|
||||
$coursetagheader = get_string('questionformtagheader', 'core_question',
|
||||
$editingcoursecontext->get_context_name(true));
|
||||
$mform->addElement('tags', 'coursetags', $coursetagheader,
|
||||
array('itemtype' => 'question', 'component' => 'core_question'));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user