Skip to content

Commit

Permalink
Merge branch 'MDL-61380-master' of git://github.com/rezaies/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Mar 7, 2018
2 parents 647db53 + 81053f8 commit 475d434
Show file tree
Hide file tree
Showing 24 changed files with 897 additions and 123 deletions.
4 changes: 4 additions & 0 deletions lib/behat/classes/partial_named_selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public function __construct() {
'xpath_element' => 'xpath_element',
'form_row' => 'form_row',
'autocomplete_selection' => 'autocomplete_selection',
'autocomplete_suggestions' => 'autocomplete_suggestions',
);

/**
Expand Down Expand Up @@ -194,6 +195,9 @@ public function __construct() {
XPATH
, 'autocomplete_selection' => <<<XPATH
.//div[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-selection', ' '))]/span[@role='listitem'][contains(normalize-space(.), %locator%)]
XPATH
, 'autocomplete_suggestions' => <<<XPATH
.//ul[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-suggestions', ' '))]/li[@role='option'][contains(normalize-space(.), %locator%)]
XPATH
);

Expand Down
20 changes: 16 additions & 4 deletions lib/tests/questionlib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ public function test_question_delete_course_category($feedback) {
public function test_question_remove_stale_questions_from_category() {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();

$dg = $this->getDataGenerator();
$course = $dg->create_course();
$quiz = $dg->create_module('quiz', ['course' => $course->id]);
Expand All @@ -407,18 +409,28 @@ public function test_question_remove_stale_questions_from_category() {

$qcat1 = $qgen->create_question_category(['contextid' => $context->id]);
$q1a = $qgen->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
$q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
$DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);

$qcat2 = $qgen->create_question_category(['contextid' => $context->id]);
$q2a = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
$q2b = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
$q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
$q2d = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will be used.
$DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
$DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
quiz_add_quiz_question($q2b->id, $quiz);
quiz_add_quiz_question($q2d->id, $quiz);
quiz_add_random_questions($quiz, 0, $qcat2->id, 1, false);

// We added one random question to the quiz and we expect the quiz to have only one random question.
$q2d = $DB->get_record_sql("SELECT q.*
FROM {question} q
JOIN {quiz_slots} s ON s.questionid = q.id
WHERE q.qtype = :qtype
AND s.quizid = :quizid",
array('qtype' => 'random', 'quizid' => $quiz->id), MUST_EXIST);

// The following 2 lines have to be after the quiz_add_random_questions() call above.
// Otherwise, quiz_add_random_questions() will to be "smart" and use them instead of creating a new "random" question.
$q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
$q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.

$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
Expand Down
10 changes: 9 additions & 1 deletion mod/quiz/addrandom.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,15 @@
'It seems a form was submitted without any button being pressed???');
}

quiz_add_random_questions($quiz, $addonpage, $categoryid, $data->numbertoadd, $includesubcategories);
if (empty($data->fromtags)) {
$data->fromtags = [];
}

$tagids = array_map(function($tagstrings) {
return (int)explode(',', $tagstrings)[0];
}, $data->fromtags);

quiz_add_random_questions($quiz, $addonpage, $categoryid, $data->numbertoadd, $includesubcategories, $tagids);
quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz);
redirect($returnurl);
Expand Down
12 changes: 12 additions & 0 deletions mod/quiz/addrandomform.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ protected function definition() {
$tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
$mform->hideIf('includesubcategories', 'category', 'in', $tops);

$tags = core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts);
$tagstrings = array();
foreach ($tags as $tag) {
$tagstrings["{$tag->id},{$tag->name}"] = $tag->name;
}
$options = array(
'multiple' => true,
'noselectionstring' => get_string('anytags', 'quiz'),
);
$mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options);
$mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz');

$mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'),
$this->get_number_of_questions_to_add_choices());

Expand Down
12 changes: 8 additions & 4 deletions mod/quiz/attemptlib.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ public function create_attempt_object($attemptdata) {
*/
public function preload_questions() {
$this->questions = question_preload_questions(null,
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page',
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page,
slot.questioncategoryid AS randomfromcategory, slot.tags AS randomfromtags,
slot.includingsubcategories AS randomincludingsubcategories',
'{quiz_slots} slot ON slot.quizid = :quizid AND q.id = slot.questionid',
array('quizid' => $this->quiz->id), 'slot.slot');
}
Expand Down Expand Up @@ -566,7 +568,7 @@ public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
$this->slots = $DB->get_records('quiz_slots',
array('quizid' => $this->get_quizid()), 'slot',
'slot, requireprevious, questionid');
'slot, requireprevious, questionid, includingsubcategories, tags');
$this->sections = array_values($DB->get_records('quiz_sections',
array('quizid' => $this->get_quizid()), 'firstslot'));

Expand Down Expand Up @@ -1871,12 +1873,14 @@ public function process_redo_question($slot, $timestamp) {
if ($questiondata->qtype != 'random') {
$newqusetionid = $questiondata->id;
} else {
$tagids = quiz_extract_random_question_tag_ids($this->slots[$slot]->tags);

$randomloader = new \core_question\bank\random_question_loader($qubaids, array());
$newqusetionid = $randomloader->get_next_question_id($questiondata->category,
(bool) $questiondata->questiontext);
(bool) $questiondata->questiontext, $tagids);
if ($newqusetionid === null) {
throw new moodle_exception('notenoughrandomquestions', 'quiz',
$quizobj->view_url(), $questiondata);
$this->quizobj->view_url(), $questiondata);
}
}

Expand Down
84 changes: 84 additions & 0 deletions mod/quiz/classes/form/randomquestion_form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Defines the editing form for random questions.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_quiz\form;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot.'/lib/formslib.php');

/**
* Class randomquestion_form
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class randomquestion_form extends \moodleform {

/**
* Form definiton.
*/
public function definition() {
$mform = $this->_form;

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

// Standard fields at the start of the form.
$mform->addElement('header', 'generalheader', get_string("general", 'form'));

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

$mform->addElement('advcheckbox', 'includesubcategories', get_string('recurse', 'quiz'), null, null, array(0, 1));

$tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
$mform->hideIf('includesubcategories', 'category', 'in', $tops);

$tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts);
$tagstrings = array();
foreach ($tags as $tag) {
$tagstrings["{$tag->id},{$tag->name}"] = $tag->name;
}
$options = array(
'multiple' => true,
'noselectionstring' => get_string('anytags', 'quiz'),
);
$mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options);
$mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz');

$mform->addElement('hidden', 'slotid');
$mform->setType('slotid', PARAM_INT);

$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);

$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('savechanges'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
}
156 changes: 156 additions & 0 deletions mod/quiz/classes/local/structure/slot_random.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Defines the \mod_quiz\local\structure\slot_random class.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_quiz\local\structure;

defined('MOODLE_INTERNAL') || die();

/**
* Class slot_random, represents a random question slot type.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class slot_random {

/** @var \stdClass Slot's properties. A record retrieved from the quiz_slots table. */
protected $record;

/**
* @var \stdClass The quiz this question slot belongs to.
*/
protected $quiz = null;

/**
* slot_random constructor.
*
* @param \stdClass $slotrecord Represents a record in the quiz_slots table.
*/
public function __construct($slotrecord = null) {
$this->record = new \stdClass();

$properties = array(
'id', 'slot', 'quizid', 'page', 'requireprevious',
'questionid', 'questioncategoryid', 'includingsubcategories',
'tags', 'maxmark');

foreach ($properties as $property) {
if (isset($slotrecord->$property)) {
$this->record->$property = $slotrecord->$property;
}
}
}

/**
* Returns the quiz for this question slot.
* The quiz is fetched the first time it is requested and then stored in a member variable to be returned each subsequent time.
*
* @return mixed
* @throws \coding_exception
*/
public function get_quiz() {
global $DB;

if (empty($this->quiz)) {
if (empty($this->record->quizid)) {
throw new \coding_exception('quizid is not set.');
}
$this->quiz = $DB->get_record('quiz', array('id' => $this->record->quizid));
}

return $this->quiz;
}

/**
* Sets the quiz object for the quiz slot.
* It is not mandatory to set the quiz as the quiz slot can fetch it the first time it is accessed,
* however it helps with the performance to set the quiz if you already have it.
*
* @param \stdClass $quiz The qui object.
*/
public function set_quiz($quiz) {
$this->quiz = $quiz;
$this->record->quizid = $quiz->id;
}

/**
* Inserts the quiz slot at the $page page.
* It is required to call this function if you are building a quiz slot object from scratch.
*
* @param int $page The page that this slot will be inserted at.
*/
public function insert($page) {
global $DB;

$slots = $DB->get_records('quiz_slots', array('quizid' => $this->record->quizid),
'slot', 'id, slot, page');

$trans = $DB->start_delegated_transaction();

$maxpage = 1;
$numonlastpage = 0;
foreach ($slots as $slot) {
if ($slot->page > $maxpage) {
$maxpage = $slot->page;
$numonlastpage = 1;
} else {
$numonlastpage += 1;
}
}

if (is_int($page) && $page >= 1) {
// Adding on a given page.
$lastslotbefore = 0;
foreach (array_reverse($slots) as $otherslot) {
if ($otherslot->page > $page) {
$DB->set_field('quiz_slots', 'slot', $otherslot->slot + 1, array('id' => $otherslot->id));
} else {
$lastslotbefore = $otherslot->slot;
break;
}
}
$this->record->slot = $lastslotbefore + 1;
$this->record->page = min($page, $maxpage + 1);

quiz_update_section_firstslots($this->record->quizid, 1, max($lastslotbefore, 1));
} else {
$lastslot = end($slots);
$quiz = $this->get_quiz();
if ($lastslot) {
$this->record->slot = $lastslot->slot + 1;
} else {
$this->record->slot = 1;
}
if ($quiz->questionsperpage && $numonlastpage >= $quiz->questionsperpage) {
$this->record->page = $maxpage + 1;
} else {
$this->record->page = $maxpage;
}
}

$this->record->id = $DB->insert_record('quiz_slots', $this->record);
$trans->allow_commit();
}
}
12 changes: 6 additions & 6 deletions mod/quiz/classes/output/edit_renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -958,16 +958,16 @@ public function question_name(structure $structure, $slot, $pageurl) {
* and also to see that category in the question bank.
*
* @param structure $structure object containing the structure of the quiz.
* @param int $slot which slot we are outputting.
* @param int $slotnumber which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function random_question(structure $structure, $slot, $pageurl) {
public function random_question(structure $structure, $slotnumber, $pageurl) {

$question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
$question = $structure->get_question_in_slot($slotnumber);
$slot = $structure->get_slot_by_number($slotnumber);
$editurl = new \moodle_url('/mod/quiz/editrandom.php',
array('returnurl' => $pageurl->out_as_local_url(), 'slotid' => $slot->id));

$temp = clone($question);
$temp->questiontext = '';
Expand Down
Loading

0 comments on commit 475d434

Please sign in to comment.