Skip to content

Commit

Permalink
MDL-32240 quiz editing: check permissions questions are added.
Browse files Browse the repository at this point in the history
We also check permissions when removing questions from the quiz, so that
we do not let users make mistakes they cannot immediately undo.

Conflicts:

	mod/quiz/editlib.php
  • Loading branch information
timhunt committed Mar 29, 2012
1 parent 29e247e commit 0175c5e
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 40 deletions.
26 changes: 23 additions & 3 deletions lib/questionlib.php
Expand Up @@ -1047,10 +1047,13 @@ function question_make_default_categories($contexts) {
} else { } else {
$category = question_get_default_category($context->id); $category = question_get_default_category($context->id);
} }
if ($preferredlevels[$context->contextlevel] > $preferredness && has_any_capability( $thispreferredness = $preferredlevels[$context->contextlevel];
array('moodle/question:usemine', 'moodle/question:useall'), $context)) { if (has_any_capability(array('moodle/question:usemine', 'moodle/question:useall'), $context)) {
$thispreferredness += 10;
}
if ($thispreferredness > $preferredness) {
$toreturn = $category; $toreturn = $category;
$preferredness = $preferredlevels[$context->contextlevel]; $preferredness = $thispreferredness;
} }
} }


Expand Down Expand Up @@ -1575,6 +1578,23 @@ public function having_one_edit_tab_cap($tabname) {
return $this->having_one_cap(self::$caps[$tabname]); return $this->having_one_cap(self::$caps[$tabname]);
} }


/**
* @return those contexts where a user can add a question and then use it.
*/
public function having_add_and_use() {
$contextswithcap = array();
foreach ($this->allcontexts as $context) {
if (!has_capability('moodle/question:add', $context)) {
continue;
}
if (!has_any_capability(array('moodle/question:useall', 'moodle/question:usemine'), $context)) {
continue;
}
$contextswithcap[] = $context;
}
return $contextswithcap;
}

/** /**
* Has at least one parent context got the cap $cap? * Has at least one parent context got the cap $cap?
* *
Expand Down
6 changes: 5 additions & 1 deletion mod/quiz/addrandom.php
Expand Up @@ -44,8 +44,12 @@
if (!$course = $DB->get_record('course', array('id' => $quiz->course))) { if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
print_error('invalidcourseid'); print_error('invalidcourseid');
} }
//you need mod/quiz:manage in addition to question capabilities to access this page. // You need mod/quiz:manage in addition to question capabilities to access this page.
// You also need the moodle/question:useall capability somewhere.
require_capability('mod/quiz:manage', $contexts->lowest()); require_capability('mod/quiz:manage', $contexts->lowest());
if (!$contexts->having_cap('moodle/question:useall')) {
print_error('nopermissions', '', '', 'use');
}


$PAGE->set_url($thispageurl); $PAGE->set_url($thispageurl);


Expand Down
5 changes: 3 additions & 2 deletions mod/quiz/addrandomform.php
Expand Up @@ -42,13 +42,14 @@ protected function definition() {
$mform =& $this->_form; $mform =& $this->_form;


$contexts = $this->_customdata; $contexts = $this->_customdata;
$usablecontexts = $contexts->having_cap('moodle/question:useall');


//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
$mform->addElement('header', 'categoryheader', $mform->addElement('header', 'categoryheader',
get_string('randomfromexistingcategory', 'quiz')); get_string('randomfromexistingcategory', 'quiz'));


$mform->addElement('questioncategory', 'category', get_string('category'), $mform->addElement('questioncategory', 'category', get_string('category'),
array('contexts' => $contexts->all(), 'top' => false)); array('contexts' => $usablecontexts, 'top' => false));


$mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz')); $mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz'));


Expand All @@ -62,7 +63,7 @@ protected function definition() {
$mform->setType('name', PARAM_MULTILANG); $mform->setType('name', PARAM_MULTILANG);


$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'), $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
array('contexts' => $contexts->all(), 'top' => true)); array('contexts' => $usablecontexts, 'top' => true));
$mform->addHelpButton('parent', 'parentcategory', 'question'); $mform->addHelpButton('parent', 'parentcategory', 'question');


$mform->addElement('submit', 'newcategory', $mform->addElement('submit', 'newcategory',
Expand Down
35 changes: 23 additions & 12 deletions mod/quiz/edit.php
Expand Up @@ -55,14 +55,15 @@
*/ */
function module_specific_buttons($cmid, $cmoptions) { function module_specific_buttons($cmid, $cmoptions) {
global $OUTPUT; global $OUTPUT;
$params = array(
'type' => 'submit',
'name' => 'add',
'value' => $OUTPUT->larrow() . ' ' . get_string('addtoquiz', 'quiz'),
);
if ($cmoptions->hasattempts) { if ($cmoptions->hasattempts) {
$disabled = 'disabled="disabled" '; $params['disabled'] = 'disabled';
} else {
$disabled = '';
} }
$out = '<input type="submit" name="add" value="' . $OUTPUT->larrow() . ' ' . return html_writer::empty_tag('input', $params);
get_string('addtoquiz', 'quiz') . '" ' . $disabled . "/>\n";
return $out;
} }


/** /**
Expand Down Expand Up @@ -137,7 +138,9 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
$quiz_reordertool = get_user_preferences('quiz_reordertab', 0); $quiz_reordertool = get_user_preferences('quiz_reordertab', 0);
} }


//will be set further down in the code $canaddrandom = $contexts->have_cap('moodle/question:useall');
$canaddquestion = (bool) $contexts->having_add_and_use();

$quizhasattempts = quiz_has_attempts($quiz->id); $quizhasattempts = quiz_has_attempts($quiz->id);


$PAGE->set_url($thispageurl); $PAGE->set_url($thispageurl);
Expand Down Expand Up @@ -211,6 +214,7 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo


if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) { if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
// Add a single question to the current quiz // Add a single question to the current quiz
quiz_require_question_use($addquestion);
$addonpage = optional_param('addonpage', 0, PARAM_INT); $addonpage = optional_param('addonpage', 0, PARAM_INT);
quiz_add_quiz_question($addquestion, $quiz, $addonpage); quiz_add_quiz_question($addquestion, $quiz, $addonpage);
quiz_delete_previews($quiz); quiz_delete_previews($quiz);
Expand All @@ -225,6 +229,7 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
foreach ($rawdata as $key => $value) { // Parse input for question ids foreach ($rawdata as $key => $value) { // Parse input for question ids
if (preg_match('!^q([0-9]+)$!', $key, $matches)) { if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
$key = $matches[1]; $key = $matches[1];
quiz_require_question_use($key);
quiz_add_quiz_question($key, $quiz); quiz_add_quiz_question($key, $quiz);
} }
} }
Expand Down Expand Up @@ -273,7 +278,11 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
} }


$remove = optional_param('remove', false, PARAM_INT); $remove = optional_param('remove', false, PARAM_INT);
if (($remove = optional_param('remove', false, PARAM_INT)) && confirm_sesskey()) { if ($remove && confirm_sesskey()) {
// Remove a question from the quiz.
// We require the user to have the 'use' capability on the question,
// so that then can add it back if they remove the wrong one by mistake.
quiz_require_question_use($remove);
quiz_remove_question($quiz, $remove); quiz_remove_question($quiz, $remove);
quiz_delete_previews($quiz); quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz); quiz_update_sumgrades($quiz);
Expand All @@ -283,7 +292,9 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
if (optional_param('quizdeleteselected', false, PARAM_BOOL) && if (optional_param('quizdeleteselected', false, PARAM_BOOL) &&
!empty($selectedquestionids) && confirm_sesskey()) { !empty($selectedquestionids) && confirm_sesskey()) {
foreach ($selectedquestionids as $questionid) { foreach ($selectedquestionids as $questionid) {
quiz_remove_question($quiz, $questionid); if (quiz_has_question_use($questionid)) {
quiz_remove_question($quiz, $questionid);
}
} }
quiz_delete_previews($quiz); quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz); quiz_update_sumgrades($quiz);
Expand Down Expand Up @@ -548,14 +559,14 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
echo '<div class="editq">'; echo '<div class="editq">';
} }


quiz_print_question_list($quiz, $thispageurl, true, quiz_print_question_list($quiz, $thispageurl, true, $quiz_reordertool, $quiz_qbanktool,
$quiz_reordertool, $quiz_qbanktool, $quizhasattempts, $defaultcategoryobj); $quizhasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom);
echo '</div>'; echo '</div>';


// Close <div class="quizcontents">: // Close <div class="quizcontents">:
echo '</div>'; echo '</div>';


if (!$quiz_reordertool) { if (!$quiz_reordertool && $canaddrandom) {
$randomform = new quiz_add_random_form(new moodle_url('/mod/quiz/addrandom.php'), $contexts); $randomform = new quiz_add_random_form(new moodle_url('/mod/quiz/addrandom.php'), $contexts);
$randomform->set_data(array( $randomform->set_data(array(
'category' => $pagevars['cat'], 'category' => $pagevars['cat'],
Expand Down
79 changes: 58 additions & 21 deletions mod/quiz/editlib.php
Expand Up @@ -35,6 +35,28 @@


define('NUM_QS_TO_SHOW_IN_RANDOM', 3); define('NUM_QS_TO_SHOW_IN_RANDOM', 3);


/**
* Verify that the question exists, and the user has permission to use it.
* Does not return. Throws an exception if the question cannot be used.
* @param int $questionid The id of the question.
*/
function quiz_require_question_use($questionid) {
global $DB;
$question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
question_require_capability_on($question, 'use');
}

/**
* Verify that the question exists, and the user has permission to use it.
* @param int $questionid The id of the question.
* @return bool whether the user can use this question.
*/
function quiz_has_question_use($questionid) {
global $DB;
$question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
return question_has_capability_on($question, 'use');
}

/** /**
* Remove a question from a quiz * Remove a question from a quiz
* @param object $quiz the quiz object. * @param object $quiz the quiz object.
Expand Down Expand Up @@ -333,19 +355,21 @@ function quiz_move_question_down($layout, $questionid) {
* Prints a list of quiz questions for the edit.php main view for edit * Prints a list of quiz questions for the edit.php main view for edit
* ($reordertool = false) and order and paging ($reordertool = true) tabs * ($reordertool = false) and order and paging ($reordertool = true) tabs
* *
* @return int sum of maximum grades
* @param object $quiz This is not the standard quiz object used elsewhere but * @param object $quiz This is not the standard quiz object used elsewhere but
* it contains the quiz layout in $quiz->questions and the grades in * it contains the quiz layout in $quiz->questions and the grades in
* $quiz->grades * $quiz->grades
* @param object $pageurl The url of the current page with the parameters required * @param moodle_url $pageurl The url of the current page with the parameters required
* for links returning to the current page, as a moodle_url object * for links returning to the current page, as a moodle_url object
* @param bool $allowdelete Indicates whether the delete icons should be displayed * @param bool $allowdelete Indicates whether the delete icons should be displayed
* @param bool $reordertool Indicates whether the reorder tool should be displayed * @param bool $reordertool Indicates whether the reorder tool should be displayed
* @param bool $quiz_qbanktool Indicates whether the question bank should be displayed * @param bool $quiz_qbanktool Indicates whether the question bank should be displayed
* @param bool $hasattempts Indicates whether the quiz has attempts * @param bool $hasattempts Indicates whether the quiz has attempts
* @param object $defaultcategoryobj
* @param bool $canaddquestion is the user able to add and use questions anywere?
* @param bool $canaddrandom is the user able to add random questions anywere?
*/ */
function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool, function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$quiz_qbanktool, $hasattempts, $defaultcategoryobj) { $quiz_qbanktool, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom) {
global $CFG, $DB, $OUTPUT; global $CFG, $DB, $OUTPUT;
$strorder = get_string('order'); $strorder = get_string('order');
$strquestionname = get_string('questionname', 'quiz'); $strquestionname = get_string('questionname', 'quiz');
Expand Down Expand Up @@ -667,7 +691,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
if (!$reordertool && !($quiz->shufflequestions && if (!$reordertool && !($quiz->shufflequestions &&
$count < $questiontotalcount - 1)) { $count < $questiontotalcount - 1)) {
quiz_print_pagecontrols($quiz, $pageurl, $pagecount, quiz_print_pagecontrols($quiz, $pageurl, $pagecount,
$hasattempts, $defaultcategoryobj); $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom);
} else if ($count < $questiontotalcount - 1) { } else if ($count < $questiontotalcount - 1) {
//do not include the last page break for reordering //do not include the last page break for reordering
//to avoid creating a new extra page in the end //to avoid creating a new extra page in the end
Expand Down Expand Up @@ -703,12 +727,19 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
* Print all the controls for adding questions directly into the * Print all the controls for adding questions directly into the
* specific page in the edit tab of edit.php * specific page in the edit tab of edit.php
* *
* @param unknown_type $quiz * @param object $quiz This is not the standard quiz object used elsewhere but
* @param unknown_type $pageurl * it contains the quiz layout in $quiz->questions and the grades in
* @param unknown_type $page * $quiz->grades
* @param unknown_type $hasattempts * @param moodle_url $pageurl The url of the current page with the parameters required
* for links returning to the current page, as a moodle_url object
* @param int $page the current page number.
* @param bool $hasattempts Indicates whether the quiz has attempts
* @param object $defaultcategoryobj
* @param bool $canaddquestion is the user able to add and use questions anywere?
* @param bool $canaddrandom is the user able to add random questions anywere?
*/ */
function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, $defaultcategoryobj) { function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts,
$defaultcategoryobj, $canaddquestion, $canaddrandom) {
global $CFG, $OUTPUT; global $CFG, $OUTPUT;
static $randombuttoncount = 0; static $randombuttoncount = 0;
$randombuttoncount++; $randombuttoncount++;
Expand All @@ -724,22 +755,25 @@ function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, $defaultc
$defaultcategoryid = $defaultcategoryobj->id; $defaultcategoryid = $defaultcategoryobj->id;
} }


// Create the url the question page will return to if ($canaddquestion) {
$returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page)); // Create the url the question page will return to

$returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page));
// Print a button linking to the choose question type page.
$returnurladdtoquiz = str_replace($CFG->wwwroot, '', $returnurladdtoquiz->out(false)); // Print a button linking to the choose question type page.
$newquestionparams = array('returnurl' => $returnurladdtoquiz, $returnurladdtoquiz = str_replace($CFG->wwwroot, '', $returnurladdtoquiz->out(false));
'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion'); $newquestionparams = array('returnurl' => $returnurladdtoquiz,
create_new_question_button($defaultcategoryid, $newquestionparams, 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion');
get_string('addaquestion', 'quiz'), create_new_question_button($defaultcategoryid, $newquestionparams,
get_string('createquestionandadd', 'quiz'), $hasattempts); get_string('addaquestion', 'quiz'),
get_string('createquestionandadd', 'quiz'), $hasattempts);
}


if ($hasattempts) { if ($hasattempts) {
$disabled = 'disabled="disabled"'; $disabled = 'disabled="disabled"';
} else { } else {
$disabled = ''; $disabled = '';
} }
if ($canaddrandom) {
?> ?>
<div class="singlebutton"> <div class="singlebutton">
<form class="randomquestionform" action="<?php echo $CFG->wwwroot; <form class="randomquestionform" action="<?php echo $CFG->wwwroot;
Expand All @@ -760,8 +794,8 @@ function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, $defaultc
</div> </div>
</form> </form>
</div> </div>
<?php echo $OUTPUT->help_icon('addarandomquestion', 'quiz'); ?> <?php echo $OUTPUT->help_icon('addarandomquestion', 'quiz');
<?php }
echo "\n</div>"; echo "\n</div>";
} }


Expand Down Expand Up @@ -1021,6 +1055,9 @@ public function get_name() {
} }


protected function display_content($question, $rowclasses) { protected function display_content($question, $rowclasses) {
if (!question_has_capability_on($question, 'use')) {
return;
}
// for RTL languages: switch right and left arrows // for RTL languages: switch right and left arrows
if (right_to_left()) { if (right_to_left()) {
$movearrow = 't/removeright'; $movearrow = 't/removeright';
Expand Down
1 change: 1 addition & 0 deletions question/editlib.php
Expand Up @@ -406,6 +406,7 @@ protected function display_content($question, $rowclasses) {
echo '<input title="' . $this->strselect . '" type="checkbox" name="q' . echo '<input title="' . $this->strselect . '" type="checkbox" name="q' .
$question->id . '" id="checkq' . $question->id . '" value="1"/>'; $question->id . '" id="checkq' . $question->id . '" value="1"/>';
if ($this->firstrow) { if ($this->firstrow) {
$PAGE->requires->js('/question/qbank.js');
$PAGE->requires->js_function_call('question_bank.init_checkbox_column', array(get_string('selectall'), $PAGE->requires->js_function_call('question_bank.init_checkbox_column', array(get_string('selectall'),
get_string('deselectall'), 'checkq' . $question->id)); get_string('deselectall'), 'checkq' . $question->id));
$this->firstrow = false; $this->firstrow = false;
Expand Down
1 change: 1 addition & 0 deletions question/question.php
Expand Up @@ -188,6 +188,7 @@
$formeditable = true; $formeditable = true;
require_capability('moodle/question:add', $categorycontext); require_capability('moodle/question:add', $categorycontext);
} }
$question->formoptions->mustbeusable = (bool) $appendqnumstring;


// Validate the question type. // Validate the question type.
$PAGE->set_pagetype('question-type-' . $question->qtype); $PAGE->set_pagetype('question-type-' . $question->qtype);
Expand Down
8 changes: 7 additions & 1 deletion question/type/edit_question_form.php
Expand Up @@ -129,9 +129,15 @@ protected function definition() {
$mform->addElement('header', 'generalheader', get_string("general", 'form')); $mform->addElement('header', 'generalheader', get_string("general", 'form'));


if (!isset($this->question->id)) { if (!isset($this->question->id)) {
if (!empty($this->question->formoptions->mustbeusable)) {
$contexts = $this->contexts->having_add_and_use();
} else {
$contexts = $this->contexts->having_cap('moodle/question:add');
}

// Adding question // Adding question
$mform->addElement('questioncategory', 'category', get_string('category', 'question'), $mform->addElement('questioncategory', 'category', get_string('category', 'question'),
array('contexts' => $this->contexts->having_cap('moodle/question:add'))); array('contexts' => $contexts));
} else if (!($this->question->formoptions->canmove || } else if (!($this->question->formoptions->canmove ||
$this->question->formoptions->cansaveasnew)) { $this->question->formoptions->cansaveasnew)) {
// Editing question with no permission to move from category. // Editing question with no permission to move from category.
Expand Down

0 comments on commit 0175c5e

Please sign in to comment.