From 0175c5efaada0da958a1bf1012fdcc6e1a308cc4 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Wed, 28 Mar 2012 13:55:17 +0100 Subject: [PATCH] MDL-32240 quiz editing: check permissions questions are added. 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 --- lib/questionlib.php | 26 +++++++-- mod/quiz/addrandom.php | 6 ++- mod/quiz/addrandomform.php | 5 +- mod/quiz/edit.php | 35 +++++++----- mod/quiz/editlib.php | 79 ++++++++++++++++++++-------- question/editlib.php | 1 + question/question.php | 1 + question/type/edit_question_form.php | 8 ++- 8 files changed, 121 insertions(+), 40 deletions(-) diff --git a/lib/questionlib.php b/lib/questionlib.php index 33f08904c2736..f9b10ae66fa91 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -1047,10 +1047,13 @@ function question_make_default_categories($contexts) { } else { $category = question_get_default_category($context->id); } - if ($preferredlevels[$context->contextlevel] > $preferredness && has_any_capability( - array('moodle/question:usemine', 'moodle/question:useall'), $context)) { + $thispreferredness = $preferredlevels[$context->contextlevel]; + if (has_any_capability(array('moodle/question:usemine', 'moodle/question:useall'), $context)) { + $thispreferredness += 10; + } + if ($thispreferredness > $preferredness) { $toreturn = $category; - $preferredness = $preferredlevels[$context->contextlevel]; + $preferredness = $thispreferredness; } } @@ -1575,6 +1578,23 @@ public function having_one_edit_tab_cap($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? * diff --git a/mod/quiz/addrandom.php b/mod/quiz/addrandom.php index c7f832cf2aad7..32a1f2cfdc576 100644 --- a/mod/quiz/addrandom.php +++ b/mod/quiz/addrandom.php @@ -44,8 +44,12 @@ if (!$course = $DB->get_record('course', array('id' => $quiz->course))) { 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()); +if (!$contexts->having_cap('moodle/question:useall')) { + print_error('nopermissions', '', '', 'use'); +} $PAGE->set_url($thispageurl); diff --git a/mod/quiz/addrandomform.php b/mod/quiz/addrandomform.php index 3d3e5c3c23d1a..0abecb3e534e1 100644 --- a/mod/quiz/addrandomform.php +++ b/mod/quiz/addrandomform.php @@ -42,13 +42,14 @@ protected function definition() { $mform =& $this->_form; $contexts = $this->_customdata; + $usablecontexts = $contexts->having_cap('moodle/question:useall'); //-------------------------------------------------------------------------------- $mform->addElement('header', 'categoryheader', get_string('randomfromexistingcategory', 'quiz')); $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')); @@ -62,7 +63,7 @@ protected function definition() { $mform->setType('name', PARAM_MULTILANG); $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->addElement('submit', 'newcategory', diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 07db4397018a1..8a5c4193df43f 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -55,14 +55,15 @@ */ function module_specific_buttons($cmid, $cmoptions) { global $OUTPUT; + $params = array( + 'type' => 'submit', + 'name' => 'add', + 'value' => $OUTPUT->larrow() . ' ' . get_string('addtoquiz', 'quiz'), + ); if ($cmoptions->hasattempts) { - $disabled = 'disabled="disabled" '; - } else { - $disabled = ''; + $params['disabled'] = 'disabled'; } - $out = '\n"; - return $out; + return html_writer::empty_tag('input', $params); } /** @@ -137,7 +138,9 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo $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); $PAGE->set_url($thispageurl); @@ -211,6 +214,7 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) { // Add a single question to the current quiz + quiz_require_question_use($addquestion); $addonpage = optional_param('addonpage', 0, PARAM_INT); quiz_add_quiz_question($addquestion, $quiz, $addonpage); quiz_delete_previews($quiz); @@ -225,6 +229,7 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo foreach ($rawdata as $key => $value) { // Parse input for question ids if (preg_match('!^q([0-9]+)$!', $key, $matches)) { $key = $matches[1]; + quiz_require_question_use($key); quiz_add_quiz_question($key, $quiz); } } @@ -273,7 +278,11 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo } $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_delete_previews($quiz); quiz_update_sumgrades($quiz); @@ -283,7 +292,9 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo if (optional_param('quizdeleteselected', false, PARAM_BOOL) && !empty($selectedquestionids) && confirm_sesskey()) { 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_update_sumgrades($quiz); @@ -548,14 +559,14 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo echo '
'; } -quiz_print_question_list($quiz, $thispageurl, true, - $quiz_reordertool, $quiz_qbanktool, $quizhasattempts, $defaultcategoryobj); +quiz_print_question_list($quiz, $thispageurl, true, $quiz_reordertool, $quiz_qbanktool, + $quizhasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom); echo '
'; // Close
: echo '
'; -if (!$quiz_reordertool) { +if (!$quiz_reordertool && $canaddrandom) { $randomform = new quiz_add_random_form(new moodle_url('/mod/quiz/addrandom.php'), $contexts); $randomform->set_data(array( 'category' => $pagevars['cat'], diff --git a/mod/quiz/editlib.php b/mod/quiz/editlib.php index 0370dc3149436..10e234f53b4cc 100644 --- a/mod/quiz/editlib.php +++ b/mod/quiz/editlib.php @@ -35,6 +35,28 @@ 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 * @param object $quiz the quiz object. @@ -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 * ($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 * it contains the quiz layout in $quiz->questions and the grades in * $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 * @param bool $allowdelete Indicates whether the delete icons 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 $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, - $quiz_qbanktool, $hasattempts, $defaultcategoryobj) { + $quiz_qbanktool, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom) { global $CFG, $DB, $OUTPUT; $strorder = get_string('order'); $strquestionname = get_string('questionname', 'quiz'); @@ -667,7 +691,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool, if (!$reordertool && !($quiz->shufflequestions && $count < $questiontotalcount - 1)) { quiz_print_pagecontrols($quiz, $pageurl, $pagecount, - $hasattempts, $defaultcategoryobj); + $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom); } else if ($count < $questiontotalcount - 1) { //do not include the last page break for reordering //to avoid creating a new extra page in the end @@ -703,12 +727,19 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool, * Print all the controls for adding questions directly into the * specific page in the edit tab of edit.php * - * @param unknown_type $quiz - * @param unknown_type $pageurl - * @param unknown_type $page - * @param unknown_type $hasattempts + * @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 + * $quiz->grades + * @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; static $randombuttoncount = 0; $randombuttoncount++; @@ -724,22 +755,25 @@ function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, $defaultc $defaultcategoryid = $defaultcategoryobj->id; } - // 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)); - $newquestionparams = array('returnurl' => $returnurladdtoquiz, - 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion'); - create_new_question_button($defaultcategoryid, $newquestionparams, - get_string('addaquestion', 'quiz'), - get_string('createquestionandadd', 'quiz'), $hasattempts); + if ($canaddquestion) { + // 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)); + $newquestionparams = array('returnurl' => $returnurladdtoquiz, + 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion'); + create_new_question_button($defaultcategoryid, $newquestionparams, + get_string('addaquestion', 'quiz'), + get_string('createquestionandadd', 'quiz'), $hasattempts); + } if ($hasattempts) { $disabled = 'disabled="disabled"'; } else { $disabled = ''; } + if ($canaddrandom) { ?>
"; } @@ -1021,6 +1055,9 @@ public function get_name() { } protected function display_content($question, $rowclasses) { + if (!question_has_capability_on($question, 'use')) { + return; + } // for RTL languages: switch right and left arrows if (right_to_left()) { $movearrow = 't/removeright'; diff --git a/question/editlib.php b/question/editlib.php index 83c8d30616963..99599f24298c8 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -406,6 +406,7 @@ protected function display_content($question, $rowclasses) { echo ''; if ($this->firstrow) { + $PAGE->requires->js('/question/qbank.js'); $PAGE->requires->js_function_call('question_bank.init_checkbox_column', array(get_string('selectall'), get_string('deselectall'), 'checkq' . $question->id)); $this->firstrow = false; diff --git a/question/question.php b/question/question.php index fecc772f94cef..4dc2025e89240 100644 --- a/question/question.php +++ b/question/question.php @@ -188,6 +188,7 @@ $formeditable = true; require_capability('moodle/question:add', $categorycontext); } +$question->formoptions->mustbeusable = (bool) $appendqnumstring; // Validate the question type. $PAGE->set_pagetype('question-type-' . $question->qtype); diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php index 0e58598fdf8d8..3bea6e639d5d0 100644 --- a/question/type/edit_question_form.php +++ b/question/type/edit_question_form.php @@ -129,9 +129,15 @@ protected function definition() { $mform->addElement('header', 'generalheader', get_string("general", 'form')); 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 $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 || $this->question->formoptions->cansaveasnew)) { // Editing question with no permission to move from category.