mirror of
https://github.com/moodle/moodle.git
synced 2025-08-07 01:46:45 +02:00
Merge branch 'master_MDL-71696-versioning-integration' of https://github.com/catalyst/moodle-MDL-70329
This commit is contained in:
commit
b841a811be
223 changed files with 7768 additions and 2899 deletions
105
mod/quiz/classes/external/submit_question_version.php
vendored
Normal file
105
mod/quiz/classes/external/submit_question_version.php
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?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/>.
|
||||
|
||||
namespace mod_quiz\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
require_once($CFG->dirroot . '/question/engine/lib.php');
|
||||
require_once($CFG->dirroot . '/question/engine/datalib.php');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
|
||||
use external_api;
|
||||
use external_description;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* External api for changing the question version in the quiz.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class submit_question_version extends external_api {
|
||||
|
||||
/**
|
||||
* Parameters for the submit_question_version.
|
||||
*
|
||||
* @return \external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters (
|
||||
[
|
||||
'slotid' => new external_value(PARAM_INT, ''),
|
||||
'newversion' => new external_value(PARAM_INT, '')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the questions slot parameters to display the question template.
|
||||
*
|
||||
* @param int $slotid Slot id to display.
|
||||
* @param int $newversion
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(int $slotid, int $newversion): array {
|
||||
global $DB;
|
||||
$params = [
|
||||
'slotid' => $slotid,
|
||||
'newversion' => $newversion
|
||||
];
|
||||
$params = self::validate_parameters(self::execute_parameters(), $params);
|
||||
$response = ['result' => false];
|
||||
// Get the required data.
|
||||
$referencedata = $DB->get_record('question_references', ['itemid' => $params['slotid']]);
|
||||
$slotdata = $DB->get_record('quiz_slots', ['id' => $slotid]);
|
||||
|
||||
// Capability check.
|
||||
list($course, $cm) = get_course_and_cm_from_instance($slotdata->quizid, 'quiz');
|
||||
$context = \context_module::instance($cm->id);
|
||||
self::validate_context($context);
|
||||
require_capability('mod/quiz:manage', $context);
|
||||
|
||||
$reference = new stdClass();
|
||||
$reference->id = $referencedata->id;
|
||||
if ($params['newversion'] === 0) {
|
||||
$reference->version = null;
|
||||
} else {
|
||||
$reference->version = $params['newversion'];
|
||||
}
|
||||
$response['result'] = $DB->update_record('question_references', $reference);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the webservice response.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function execute_returns() {
|
||||
return new external_single_structure(
|
||||
[
|
||||
'result' => new external_value(PARAM_BOOL, '')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,23 +14,14 @@
|
|||
// 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 <shamim@moodle.com>
|
||||
* @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 <shamim@moodle.com>
|
||||
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class slot_random {
|
||||
|
@ -38,6 +29,11 @@ class slot_random {
|
|||
/** @var \stdClass Slot's properties. A record retrieved from the quiz_slots table. */
|
||||
protected $record;
|
||||
|
||||
/**
|
||||
* @var \stdClass set reference record
|
||||
*/
|
||||
protected $referencerecord;
|
||||
|
||||
/**
|
||||
* @var \stdClass The quiz this question slot belongs to.
|
||||
*/
|
||||
|
@ -48,6 +44,11 @@ class slot_random {
|
|||
*/
|
||||
protected $tags = [];
|
||||
|
||||
/**
|
||||
* @var string filter condition
|
||||
*/
|
||||
protected $filtercondition = null;
|
||||
|
||||
/**
|
||||
* slot_random constructor.
|
||||
*
|
||||
|
@ -55,16 +56,22 @@ class slot_random {
|
|||
*/
|
||||
public function __construct($slotrecord = null) {
|
||||
$this->record = new \stdClass();
|
||||
$this->referencerecord = new \stdClass();
|
||||
|
||||
$properties = array(
|
||||
'id', 'slot', 'quizid', 'page', 'requireprevious', 'questionid',
|
||||
'questioncategoryid', 'includingsubcategories', 'maxmark');
|
||||
$slotproperties = ['id', 'slot', 'quizid', 'page', 'requireprevious', 'maxmark'];
|
||||
$setreferenceproperties = ['usingcontextid', 'questionscontextid'];
|
||||
|
||||
foreach ($properties as $property) {
|
||||
foreach ($slotproperties as $property) {
|
||||
if (isset($slotrecord->$property)) {
|
||||
$this->record->$property = $slotrecord->$property;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($setreferenceproperties as $referenceproperty) {
|
||||
if (isset($slotrecord->$referenceproperty)) {
|
||||
$this->referencerecord->$referenceproperty = $slotrecord->$referenceproperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,6 +129,19 @@ class slot_random {
|
|||
$this->tags = \core_tag_tag::get_bulk($tagids, 'id, name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter condition.
|
||||
*
|
||||
* @param \stdClass $filters
|
||||
*/
|
||||
public function set_filter_condition($filters) {
|
||||
if (!empty($this->tags)) {
|
||||
$filters->tags = $this->tags;
|
||||
}
|
||||
|
||||
$this->filtercondition = json_encode($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -179,17 +199,11 @@ class slot_random {
|
|||
|
||||
$this->record->id = $DB->insert_record('quiz_slots', $this->record);
|
||||
|
||||
if (!empty($this->tags)) {
|
||||
$recordstoinsert = [];
|
||||
foreach ($this->tags as $tag) {
|
||||
$recordstoinsert[] = (object)[
|
||||
'slotid' => $this->record->id,
|
||||
'tagid' => $tag->id,
|
||||
'tagname' => $tag->name
|
||||
];
|
||||
}
|
||||
$DB->insert_records('quiz_slot_tags', $recordstoinsert);
|
||||
}
|
||||
$this->referencerecord->component = 'mod_quiz';
|
||||
$this->referencerecord->questionarea = 'slot';
|
||||
$this->referencerecord->itemid = $this->record->id;
|
||||
$this->referencerecord->filtercondition = $this->filtercondition;
|
||||
$DB->insert_record('question_set_references', $this->referencerecord);
|
||||
|
||||
$trans->allow_commit();
|
||||
|
||||
|
|
|
@ -25,8 +25,10 @@
|
|||
namespace mod_quiz\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
use \mod_quiz\structure;
|
||||
use \html_writer;
|
||||
use \qbank_previewquestion\helper;
|
||||
use renderable;
|
||||
|
||||
/**
|
||||
|
@ -46,13 +48,13 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
*
|
||||
* @param \quiz $quizobj object containing all the quiz settings information.
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @param array $pagevars the variables from {@link question_edit_setup()}.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function edit_page(\quiz $quizobj, structure $structure,
|
||||
\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
|
||||
\core_question\local\bank\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
|
||||
$output = '';
|
||||
|
||||
// Information at the top.
|
||||
|
@ -493,7 +495,7 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \stdClass $section information about the section.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
|
@ -513,7 +515,7 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot which slot we are outputting.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
|
@ -546,7 +548,7 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot the first slot on the page we are outputting.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
|
@ -580,12 +582,12 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $page the page number that this menu will add to.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function add_menu_actions(structure $structure, $page, \moodle_url $pageurl,
|
||||
\question_edit_contexts $contexts, array $pagevars) {
|
||||
\core_question\local\bank\question_edit_contexts $contexts, array $pagevars) {
|
||||
|
||||
$actions = $this->edit_menu_actions($structure, $page, $pageurl, $pagevars);
|
||||
if (empty($actions)) {
|
||||
|
@ -727,7 +729,11 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function question(structure $structure, $slot, \moodle_url $pageurl) {
|
||||
public function question(structure $structure, int $slot, \moodle_url $pageurl) {
|
||||
global $DB;
|
||||
// Get the data required by the question_slot template.
|
||||
$slotid = $structure->get_slot_id_for_slot($slot);
|
||||
|
||||
$output = '';
|
||||
$output .= html_writer::start_tag('div');
|
||||
|
||||
|
@ -735,50 +741,92 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
$output .= $this->question_move_icon($structure, $slot);
|
||||
}
|
||||
|
||||
$output .= html_writer::start_div('mod-indent-outer');
|
||||
$checkbox = new \core\output\checkbox_toggleall($this->togglegroup, false, [
|
||||
'id' => 'selectquestion-' . $structure->get_displayed_number_for_slot($slot),
|
||||
'name' => 'selectquestion[]',
|
||||
'value' => $structure->get_displayed_number_for_slot($slot),
|
||||
'classes' => 'select-multiple-checkbox',
|
||||
]);
|
||||
$output .= $this->render($checkbox);
|
||||
$output .= $this->question_number($structure->get_displayed_number_for_slot($slot));
|
||||
$data = [
|
||||
'slotid' => $slotid,
|
||||
'canbeedited' => $structure->can_be_edited(),
|
||||
'checkbox' => $this->get_checkbox_render($structure, $slot),
|
||||
'questionnumber' => $this->question_number($structure->get_displayed_number_for_slot($slot)),
|
||||
'questionname' => $this->get_question_name_for_slot($structure, $slot, $pageurl),
|
||||
'questionicons' => $this->get_action_icon($structure, $slot, $pageurl),
|
||||
'questiondependencyicon' => ($structure->can_be_edited() ? $this->question_dependency_icon($structure, $slot) : ''),
|
||||
'versionselection' => false
|
||||
];
|
||||
|
||||
// This div is used to indent the content.
|
||||
$output .= html_writer::div('', 'mod-indent');
|
||||
$data['versionoptions'] = [];
|
||||
if ($structure->get_slot_by_number($slot)->qtype !== 'random') {
|
||||
$data['versionselection'] = true;
|
||||
$data['versionoption'] = qbank_helper::get_question_version_info($structure->get_question_in_slot($slot)->id, $slotid);
|
||||
$this->page->requires->js_call_amd('mod_quiz/question_slot', 'init', [$slotid]);
|
||||
}
|
||||
|
||||
// Render the question slot template.
|
||||
$output .= $this->render_from_template('mod_quiz/question_slot', $data);
|
||||
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the checkbox render.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot the slot on the page we are outputting.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function get_checkbox_render(structure $structure, int $slot) : string {
|
||||
$checkbox = new \core\output\checkbox_toggleall($this->togglegroup, false,
|
||||
[
|
||||
'id' => 'selectquestion-' . $structure->get_displayed_number_for_slot($slot),
|
||||
'name' => 'selectquestion[]',
|
||||
'value' => $structure->get_displayed_number_for_slot($slot),
|
||||
'classes' => 'select-multiple-checkbox',
|
||||
]);
|
||||
|
||||
return $this->render($checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question name for the slot.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot the slot on the page we are outputting.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function get_question_name_for_slot(structure $structure, int $slot, \moodle_url $pageurl) : string {
|
||||
// Display the link to the question (or do nothing if question has no url).
|
||||
if ($structure->get_question_type_for_slot($slot) == 'random') {
|
||||
if ($structure->get_question_type_for_slot($slot) === 'random') {
|
||||
$questionname = $this->random_question($structure, $slot, $pageurl);
|
||||
} else {
|
||||
$questionname = $this->question_name($structure, $slot, $pageurl);
|
||||
}
|
||||
|
||||
// Start the div for the activity title, excluding the edit icons.
|
||||
$output .= html_writer::start_div('activityinstance');
|
||||
$output .= $questionname;
|
||||
|
||||
// Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
|
||||
$output .= html_writer::end_tag('div'); // .activityinstance.
|
||||
return $questionname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action icons render.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot the slot on the page we are outputting.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function get_action_icon(structure $structure, int $slot, \moodle_url $pageurl) : string {
|
||||
// Action icons.
|
||||
$qtype = $structure->get_question_type_for_slot($slot);
|
||||
$questionicons = '';
|
||||
$questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot));
|
||||
if ($qtype !== 'random') {
|
||||
$questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot),
|
||||
null, null, $qtype);
|
||||
}
|
||||
if ($structure->can_be_edited()) {
|
||||
$questionicons .= $this->question_remove_icon($structure, $slot, $pageurl);
|
||||
}
|
||||
$questionicons .= $this->marked_out_of_field($structure, $slot);
|
||||
$output .= html_writer::span($questionicons, 'actions'); // Required to add js spinner icon.
|
||||
if ($structure->can_be_edited()) {
|
||||
$output .= $this->question_dependency_icon($structure, $slot);
|
||||
}
|
||||
|
||||
// End of indentation div.
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
return $output;
|
||||
return $questionicons;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -814,9 +862,10 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* @param \stdClass $question data from the question and quiz_slots tables.
|
||||
* @param bool $label if true, show the preview question label after the icon
|
||||
* @param int $variant which question variant to preview (optional).
|
||||
* @param string $qtype the type of question
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function question_preview_icon($quiz, $question, $label = null, $variant = null) {
|
||||
public function question_preview_icon($quiz, $question, $label = null, $variant = null, $qtype = null) {
|
||||
$url = quiz_question_preview_url($quiz, $question, $variant);
|
||||
|
||||
// Do we want a label?
|
||||
|
@ -975,10 +1024,8 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* @return string HTML to output.
|
||||
*/
|
||||
public function random_question(structure $structure, $slotnumber, $pageurl) {
|
||||
|
||||
$question = $structure->get_question_in_slot($slotnumber);
|
||||
$slot = $structure->get_slot_by_number($slotnumber);
|
||||
$slottags = $structure->get_slot_tags_for_slot_id($slot->id);
|
||||
$editurl = new \moodle_url('/mod/quiz/editrandom.php',
|
||||
array('returnurl' => $pageurl->out_as_local_url(), 'slotid' => $slot->id));
|
||||
|
||||
|
@ -986,6 +1033,9 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
$temp->questiontext = '';
|
||||
$instancename = quiz_question_tostring($temp);
|
||||
|
||||
$setreference = qbank_helper::get_random_question_data_from_slot($slot->id);
|
||||
$filtercondition = json_decode($setreference->filtercondition);
|
||||
|
||||
$configuretitle = get_string('configurerandomquestion', 'quiz');
|
||||
$qtype = \question_bank::get_qtype($question->qtype, false);
|
||||
$namestr = $qtype->local_name();
|
||||
|
@ -994,20 +1044,25 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
|
||||
$editicon = $this->pix_icon('t/edit', $configuretitle, 'moodle', array('title' => ''));
|
||||
$qbankurlparams = array(
|
||||
'cmid' => $structure->get_cmid(),
|
||||
'cat' => $question->category . ',' . $question->contextid,
|
||||
'recurse' => !empty($question->questiontext)
|
||||
'cmid' => $structure->get_cmid(),
|
||||
'cat' => $filtercondition->questioncategoryid . ',' . $setreference->questionscontextid,
|
||||
'recurse' => !empty($setreference->questionscontextid)
|
||||
);
|
||||
|
||||
$slottags = [];
|
||||
if (isset($filtercondition->tags)) {
|
||||
$slottags = $filtercondition->tags;
|
||||
}
|
||||
foreach ($slottags as $index => $slottag) {
|
||||
$qbankurlparams["qtagids[{$index}]"] = $slottag->tagid;
|
||||
$slottag = explode(',', $slottag);
|
||||
$qbankurlparams["qtagids[{$index}]"] = $slottag[0];
|
||||
}
|
||||
|
||||
// If this is a random question, display a link to show the questions
|
||||
// selected from in the question bank.
|
||||
$qbankurl = new \moodle_url('/question/edit.php', $qbankurlparams);
|
||||
$qbanklink = ' ' . \html_writer::link($qbankurl,
|
||||
get_string('seequestions', 'quiz'), array('class' => 'mod_quiz_random_qbank_link'));
|
||||
get_string('seequestions', 'quiz'), array('class' => 'mod_quiz_random_qbank_link'));
|
||||
|
||||
return html_writer::link($editurl, $icon . $editicon, array('title' => $configuretitle)) .
|
||||
' ' . $instancename . ' ' . $qbanklink;
|
||||
|
@ -1083,13 +1138,13 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* is handled with the specific code for those.)
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return bool Always returns true
|
||||
*/
|
||||
protected function initialise_editing_javascript(structure $structure,
|
||||
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
\core_question\local\bank\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
|
||||
$config = new \stdClass();
|
||||
$config->resourceurl = '/mod/quiz/edit_rest.php';
|
||||
|
@ -1189,13 +1244,13 @@ class edit_renderer extends \plugin_renderer_base {
|
|||
* HTML for a page, with ids stripped, so it can be used as a javascript template.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML for a new page.
|
||||
*/
|
||||
protected function new_page_template(structure $structure,
|
||||
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
\core_question\local\bank\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
if (!$structure->has_questions()) {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
|
||||
namespace mod_quiz\question\bank;
|
||||
|
||||
use core_question\local\bank\question_version_status;
|
||||
use mod_quiz\question\bank\filter\custom_category_condition;
|
||||
|
||||
/**
|
||||
* Subclass to customise the view of the question bank for the quiz editing screen.
|
||||
*
|
||||
|
@ -44,8 +47,8 @@ class custom_view extends \core_question\local\bank\view {
|
|||
const MAX_TEXT_LENGTH = 200;
|
||||
|
||||
/**
|
||||
* Constructor for custom_view.
|
||||
* @param \question_edit_contexts $contexts
|
||||
* Constructor.
|
||||
* @param \core_question\local\bank\question_edit_contexts $contexts
|
||||
* @param \moodle_url $pageurl
|
||||
* @param \stdClass $course course settings
|
||||
* @param \stdClass $cm activity settings.
|
||||
|
@ -251,4 +254,81 @@ class custom_view extends \core_question\local\bank\view {
|
|||
$this->sort[$sort] = $order;
|
||||
}
|
||||
}
|
||||
|
||||
protected function build_query(): void {
|
||||
// Get the required tables and fields.
|
||||
$joins = [];
|
||||
$fields = ['qv.status', 'qc.id', 'qv.version', 'qv.id as versionid', 'qbe.id as questionbankentryid'];
|
||||
if (!empty($this->requiredcolumns)) {
|
||||
foreach ($this->requiredcolumns as $column) {
|
||||
$extrajoins = $column->get_extra_joins();
|
||||
foreach ($extrajoins as $prefix => $join) {
|
||||
if (isset($joins[$prefix]) && $joins[$prefix] != $join) {
|
||||
throw new \coding_exception('Join ' . $join . ' conflicts with previous join ' . $joins[$prefix]);
|
||||
}
|
||||
$joins[$prefix] = $join;
|
||||
}
|
||||
$fields = array_merge($fields, $column->get_required_fields());
|
||||
}
|
||||
}
|
||||
$fields = array_unique($fields);
|
||||
|
||||
// Build the order by clause.
|
||||
$sorts = [];
|
||||
foreach ($this->sort as $sort => $order) {
|
||||
list($colname, $subsort) = $this->parse_subsort($sort);
|
||||
$sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort);
|
||||
}
|
||||
|
||||
// Build the where clause.
|
||||
$latestversion = 'qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)';
|
||||
$readyonly = "qv.status = '" . question_version_status::QUESTION_STATUS_READY . "' ";
|
||||
$tests = ['q.parent = 0', $latestversion, $readyonly];
|
||||
$this->sqlparams = [];
|
||||
foreach ($this->searchconditions as $searchcondition) {
|
||||
if ($searchcondition->where()) {
|
||||
$tests[] = '((' . $searchcondition->where() .'))';
|
||||
}
|
||||
if ($searchcondition->params()) {
|
||||
$this->sqlparams = array_merge($this->sqlparams, $searchcondition->params());
|
||||
}
|
||||
}
|
||||
// Build the SQL.
|
||||
$sql = ' FROM {question} q ' . implode(' ', $joins);
|
||||
$sql .= ' WHERE ' . implode(' AND ', $tests);
|
||||
$this->countsql = 'SELECT count(1)' . $sql;
|
||||
$this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts);
|
||||
}
|
||||
|
||||
public function wanted_filters($cat, $tagids, $showhidden, $recurse, $editcontexts, $showquestiontext): void {
|
||||
global $CFG;
|
||||
list(, $contextid) = explode(',', $cat);
|
||||
$catcontext = \context::instance_by_id($contextid);
|
||||
$thiscontext = $this->get_most_specific_context();
|
||||
// Category selection form.
|
||||
$this->display_question_bank_header();
|
||||
|
||||
// Display tag filter if usetags setting is enabled/enablefilters is true.
|
||||
if ($this->enablefilters) {
|
||||
if (is_array($this->customfilterobjects)) {
|
||||
foreach ($this->customfilterobjects as $filterobjects) {
|
||||
$this->searchconditions[] = $filterobjects;
|
||||
}
|
||||
} else {
|
||||
if ($CFG->usetags) {
|
||||
array_unshift($this->searchconditions,
|
||||
new \core_question\bank\search\tag_condition([$catcontext, $thiscontext], $tagids));
|
||||
}
|
||||
|
||||
array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden));
|
||||
array_unshift($this->searchconditions, new custom_category_condition(
|
||||
$cat, $recurse, $editcontexts, $this->baseurl, $this->course));
|
||||
}
|
||||
}
|
||||
$this->display_options_form($showquestiontext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?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/>.
|
||||
|
||||
namespace mod_quiz\question\bank\filter;
|
||||
|
||||
/**
|
||||
* A custom filter condition for quiz to select question categories.
|
||||
*
|
||||
* This is required as quiz will only use ready questions and the count should show according to that.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category question
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_category_condition extends \core_question\bank\search\category_condition {
|
||||
|
||||
public function display_options() {
|
||||
global $PAGE;
|
||||
$displaydata = [];
|
||||
$catmenu = custom_category_condition_helper::question_category_options($this->contexts, true, 0,
|
||||
true, -1, false);
|
||||
$displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [],
|
||||
['class' => 'searchoptions custom-select', 'id' => 'id_selectacategory']);
|
||||
$displaydata['categorydesc'] = '';
|
||||
if ($this->category) {
|
||||
$displaydata['categorydesc'] = $this->print_category_info($this->category);
|
||||
}
|
||||
return $PAGE->get_renderer('core_question', 'bank')->render_category_condition($displaydata);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?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/>.
|
||||
|
||||
namespace mod_quiz\question\bank\filter;
|
||||
|
||||
use core_question\local\bank\question_version_status;
|
||||
|
||||
/**
|
||||
* A custom filter condition helper for quiz to select question categories.
|
||||
*
|
||||
* This is required as quiz will only use ready questions and the count should show according to that.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category question
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_category_condition_helper extends \qbank_managecategories\helper {
|
||||
|
||||
public static function question_category_options(array $contexts, bool $top = false, int $currentcat = 0,
|
||||
bool $popupform = false, int $nochildrenof = -1,
|
||||
bool $escapecontextnames = true): array {
|
||||
global $CFG;
|
||||
$pcontexts = [];
|
||||
foreach ($contexts as $context) {
|
||||
$pcontexts[] = $context->id;
|
||||
}
|
||||
$contextslist = join(', ', $pcontexts);
|
||||
|
||||
$categories = self::get_categories_for_contexts($contextslist, 'parent, sortorder, name ASC', $top);
|
||||
|
||||
if ($top) {
|
||||
$categories = self::question_fix_top_names($categories);
|
||||
}
|
||||
|
||||
$categories = self::question_add_context_in_key($categories);
|
||||
$categories = self::add_indented_names($categories, $nochildrenof);
|
||||
|
||||
// Sort cats out into different contexts.
|
||||
$categoriesarray = [];
|
||||
foreach ($pcontexts as $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
$contextstring = $context->get_context_name(true, true, $escapecontextnames);
|
||||
foreach ($categories as $category) {
|
||||
if ($category->contextid == $contextid) {
|
||||
$cid = $category->id;
|
||||
if ($currentcat != $cid || $currentcat == 0) {
|
||||
$a = new \stdClass;
|
||||
$a->name = format_string($category->indentedname, true,
|
||||
['context' => $context]);
|
||||
if ($category->idnumber !== null && $category->idnumber !== '') {
|
||||
$a->idnumber = s($category->idnumber);
|
||||
}
|
||||
if (!empty($category->questioncount)) {
|
||||
$a->questioncount = $category->questioncount;
|
||||
}
|
||||
if (isset($a->idnumber) && isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithidnumberandcount', 'question', $a);
|
||||
} else if (isset($a->idnumber)) {
|
||||
$formattedname = get_string('categorynamewithidnumber', 'question', $a);
|
||||
} else if (isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithcount', 'question', $a);
|
||||
} else {
|
||||
$formattedname = $a->name;
|
||||
}
|
||||
$categoriesarray[$contextstring][$cid] = $formattedname;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($popupform) {
|
||||
$popupcats = [];
|
||||
foreach ($categoriesarray as $contextstring => $optgroup) {
|
||||
$group = [];
|
||||
foreach ($optgroup as $key => $value) {
|
||||
$key = str_replace($CFG->wwwroot, '', $key);
|
||||
$group[$key] = $value;
|
||||
}
|
||||
$popupcats[] = [$contextstring => $group];
|
||||
}
|
||||
return $popupcats;
|
||||
} else {
|
||||
return $categoriesarray;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_categories_for_contexts($contexts, string $sortorder = 'parent, sortorder, name ASC',
|
||||
bool $top = false, int $showallversions = 0): array {
|
||||
global $DB;
|
||||
$topwhere = $top ? '' : 'AND c.parent <> 0';
|
||||
$statuscondition = "AND qv.status = '". question_version_status::QUESTION_STATUS_READY . "' ";
|
||||
|
||||
$sql = "SELECT c.*,
|
||||
(SELECT COUNT(1)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.parent = '0'
|
||||
$statuscondition
|
||||
AND c.id = qbe.questioncategoryid
|
||||
AND ($showallversions = 1
|
||||
OR (qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)
|
||||
)
|
||||
)
|
||||
) AS questioncount
|
||||
FROM {question_categories} c
|
||||
WHERE c.contextid IN ($contexts) $topwhere
|
||||
ORDER BY $sortorder";
|
||||
|
||||
return $DB->get_records_sql($sql);
|
||||
}
|
||||
}
|
427
mod/quiz/classes/question/bank/qbank_helper.php
Normal file
427
mod/quiz/classes/question/bank/qbank_helper.php
Normal file
|
@ -0,0 +1,427 @@
|
|||
<?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/>.
|
||||
|
||||
namespace mod_quiz\question\bank;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
|
||||
|
||||
/**
|
||||
* Helper class for question bank and its associated data.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category question
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qbank_helper {
|
||||
|
||||
/**
|
||||
* Check if the slot is a random question or not.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_random($slotid): bool {
|
||||
global $DB;
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
];
|
||||
return $DB->record_exists('question_set_references', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version options for the question.
|
||||
*
|
||||
* @param int $questionid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_version_options($questionid): array {
|
||||
global $DB;
|
||||
$sql = "SELECT qv.id AS versionid, qv.version
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.questionbankentryid = (SELECT DISTINCT qbe.id
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q ON qv.questionid = q.id
|
||||
WHERE q.id = ?)
|
||||
ORDER BY qv.version DESC";
|
||||
|
||||
return $DB->get_records_sql($sql, [$questionid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the elements of an array according to a key.
|
||||
*
|
||||
* @param array $arrays
|
||||
* @param string $on
|
||||
* @param int $order
|
||||
* @return array
|
||||
*/
|
||||
public static function question_array_sort($arrays, $on, $order = SORT_ASC): array {
|
||||
$element = [];
|
||||
foreach ($arrays as $array) {
|
||||
$element[$array->$on] = $array;
|
||||
}
|
||||
ksort($element, $order);
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question id from slot id.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_question_for_redo($slotid) {
|
||||
global $DB;
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
];
|
||||
$referencerecord = $DB->get_record('question_references', $params);
|
||||
if ($referencerecord->version === null) {
|
||||
$questionsql = 'SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
WHERE qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qv.questionbankentryid)
|
||||
AND qv.questionbankentryid = ?';
|
||||
$questionid = $DB->get_record_sql($questionsql, [$referencerecord->questionbankentryid])->id;
|
||||
} else {
|
||||
$questionid = $DB->get_field('question_versions', 'questionid',
|
||||
['questionbankentryid' => $referencerecord->questionbankentryid,
|
||||
'version' => $referencerecord->version]);
|
||||
}
|
||||
return $questionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random question object from the slot id.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return false|mixed|\stdClass
|
||||
*/
|
||||
public static function get_random_question_data_from_slot($slotid) {
|
||||
global $DB;
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
];
|
||||
return $DB->get_record('question_set_references', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question ids for specific question version.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_specific_version_question_ids($quizid) {
|
||||
global $DB;
|
||||
$questionids = [];
|
||||
$sql = 'SELECT qv.questionid
|
||||
FROM {quiz_slots} qs
|
||||
JOIN {question_references} qr ON qr.itemid = qs.id
|
||||
JOIN {question_versions} qv ON qv.questionbankentryid = qr.questionbankentryid
|
||||
AND qv.version = qr.version
|
||||
WHERE qr.version IS NOT NULL
|
||||
AND qs.quizid = ?
|
||||
AND qr.component = ?
|
||||
AND qr.questionarea = ?';
|
||||
$questions = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
foreach ($questions as $question) {
|
||||
$questionids [] = $question->questionid;
|
||||
}
|
||||
return $questionids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question ids for always latest options.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_always_latest_version_question_ids($quizid) {
|
||||
global $DB;
|
||||
$questionids = [];
|
||||
$sql = 'SELECT qr.questionbankentryid as entry
|
||||
FROM {quiz_slots} qs
|
||||
JOIN {question_references} qr ON qr.itemid = qs.id
|
||||
WHERE qr.version IS NULL
|
||||
AND qs.quizid = ?
|
||||
AND qr.component = ?
|
||||
AND qr.questionarea = ?';
|
||||
$entryids = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
$questionentries = [];
|
||||
foreach ($entryids as $entryid) {
|
||||
$questionentries [] = $entryid->entry;
|
||||
}
|
||||
if (empty($questionentries)) {
|
||||
return $questionids;
|
||||
}
|
||||
list($questionidcondition, $params) = $DB->get_in_or_equal($questionentries);
|
||||
$extracondition = 'AND qv.questionbankentryid ' . $questionidcondition;
|
||||
$questionsql = "SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
WHERE qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qv.questionbankentryid)
|
||||
$extracondition";
|
||||
$questions = $DB->get_records_sql($questionsql, $params);
|
||||
foreach ($questions as $question) {
|
||||
$questionids [] = $question->id;
|
||||
}
|
||||
return $questionids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question structure data for the given quiz or question ids.
|
||||
*
|
||||
* @param null $quizid
|
||||
* @param array $questionids
|
||||
* @param bool $attempt
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_structure_data($quizid, $questionids = [], $attempt = false) {
|
||||
global $DB;
|
||||
$params = ['quizid' => $quizid];
|
||||
$condition = '';
|
||||
$joinon = 'AND qr.version = qv.version';
|
||||
if (!empty($questionids)) {
|
||||
list($condition, $param) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED, 'questionid');
|
||||
$condition = 'AND q.id ' . $condition;
|
||||
$joinon = '';
|
||||
$params = array_merge($params, $param);
|
||||
}
|
||||
if ($attempt) {
|
||||
$selectstart = 'q.*, slot.id AS slotid, slot.slot,';
|
||||
} else {
|
||||
$selectstart = 'slot.slot, slot.id AS slotid, q.*,';
|
||||
}
|
||||
$sql = "SELECT $selectstart
|
||||
q.id AS questionid,
|
||||
q.name,
|
||||
q.qtype,
|
||||
q.length,
|
||||
slot.page,
|
||||
slot.maxmark,
|
||||
slot.requireprevious,
|
||||
qc.id as category,
|
||||
qc.contextid,qv.status,
|
||||
qv.id as versionid,
|
||||
qv.version,
|
||||
qv.questionbankentryid
|
||||
FROM {quiz_slots} slot
|
||||
LEFT JOIN {question_references} qr ON qr.itemid = slot.id AND qr.component = 'mod_quiz' AND qr.questionarea = 'slot'
|
||||
LEFT JOIN {question_bank_entries} qbe ON qbe.id = qr.questionbankentryid
|
||||
LEFT JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id $joinon
|
||||
LEFT JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
LEFT JOIN {question} q ON q.id = qv.questionid
|
||||
WHERE slot.quizid = :quizid
|
||||
$condition";
|
||||
$questiondatas = $DB->get_records_sql($sql, $params);
|
||||
foreach ($questiondatas as $questiondata) {
|
||||
$questiondata->_partiallyloaded = true;
|
||||
}
|
||||
if (!empty($questiondatas)) {
|
||||
return $questiondatas;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get question structure.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_structure($quizid) {
|
||||
$firstslotsets = self::get_question_structure_data($quizid);
|
||||
$latestquestionids = self::get_always_latest_version_question_ids($quizid);
|
||||
$secondslotsets = self::get_question_structure_data($quizid, $latestquestionids);
|
||||
foreach ($firstslotsets as $key => $firstslotset) {
|
||||
foreach ($secondslotsets as $secondslotset) {
|
||||
if ($firstslotset->slotid === $secondslotset->slotid) {
|
||||
unset($firstslotsets[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::question_array_sort(array_merge($firstslotsets, $secondslotsets), 'slot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load random questions.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @param array $questiondata
|
||||
* @return array
|
||||
*/
|
||||
public static function question_load_random_questions($quizid, $questiondata) {
|
||||
global $DB, $USER;
|
||||
$sql = 'SELECT slot.id AS slotid,
|
||||
slot.maxmark,
|
||||
slot.slot,
|
||||
slot.page,
|
||||
qsr.filtercondition
|
||||
FROM {question_set_references} qsr
|
||||
JOIN {quiz_slots} slot ON slot.id = qsr.itemid
|
||||
WHERE slot.quizid = ?
|
||||
AND qsr.component = ?
|
||||
AND qsr.questionarea = ?';
|
||||
$randomquestiondatas = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
|
||||
$randomquestions = [];
|
||||
// Questions already added.
|
||||
$usedquestionids = [];
|
||||
foreach ($questiondata as $question) {
|
||||
if (isset($usedquestions[$question->id])) {
|
||||
$usedquestionids[$question->id] += 1;
|
||||
} else {
|
||||
$usedquestionids[$question->id] = 1;
|
||||
}
|
||||
}
|
||||
// Usages for this user's previous quiz attempts.
|
||||
$qubaids = new \mod_quiz\question\qubaids_for_users_attempts($quizid, $USER->id);
|
||||
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, $usedquestionids);
|
||||
|
||||
foreach ($randomquestiondatas as $randomquestiondata) {
|
||||
$filtercondition = json_decode($randomquestiondata->filtercondition);
|
||||
$tagids = [];
|
||||
if (isset($filtercondition->tags)) {
|
||||
foreach ($filtercondition->tags as $tag) {
|
||||
$tagstring = explode(',', $tag);
|
||||
$tagids [] = $tagstring[0];
|
||||
}
|
||||
}
|
||||
$randomquestiondata->randomfromcategory = $filtercondition->questioncategoryid;
|
||||
$randomquestiondata->randomincludingsubcategories = $filtercondition->includingsubcategories;
|
||||
$randomquestiondata->questionid = $randomloader->get_next_question_id($randomquestiondata->randomfromcategory,
|
||||
$randomquestiondata->randomincludingsubcategories, $tagids);
|
||||
$randomquestions [] = $randomquestiondata;
|
||||
}
|
||||
|
||||
foreach ($randomquestions as $randomquestion) {
|
||||
// Should not add if there is no question found from the ramdom question loader, maybe empty category.
|
||||
if ($randomquestion->questionid === null) {
|
||||
continue;
|
||||
}
|
||||
$question = new \stdClass();
|
||||
$question->slotid = $randomquestion->slotid;
|
||||
$question->maxmark = $randomquestion->maxmark;
|
||||
$question->slot = $randomquestion->slot;
|
||||
$question->page = $randomquestion->page;
|
||||
$qdatas = question_preload_questions($randomquestion->questionid);
|
||||
$qdatas = reset($qdatas);
|
||||
foreach ($qdatas as $key => $qdata) {
|
||||
$question->$key = $qdata;
|
||||
}
|
||||
$questiondata[$question->id] = $question;
|
||||
}
|
||||
|
||||
return $questiondata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose question for redo.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @param \qubaid_condition $qubaids
|
||||
* @return int
|
||||
*/
|
||||
public static function choose_question_for_redo($slotid, $qubaids): int {
|
||||
// Choose the replacement question.
|
||||
if (!self::is_random($slotid)) {
|
||||
$newqusetionid = self::get_question_for_redo($slotid);
|
||||
} else {
|
||||
$tagids = [];
|
||||
$randomquestiondata = self::get_random_question_data_from_slot($slotid);
|
||||
$filtercondition = json_decode($randomquestiondata->filtercondition);
|
||||
if (isset($filtercondition->tags)) {
|
||||
foreach ($filtercondition->tags as $tag) {
|
||||
$tagstring = explode(',', $tag);
|
||||
$tagids [] = $tagstring[0];
|
||||
}
|
||||
}
|
||||
|
||||
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, []);
|
||||
$newqusetionid = $randomloader->get_next_question_id($filtercondition->questioncategoryid,
|
||||
(bool) $filtercondition->includingsubcategories, $tagids);
|
||||
if ($newqusetionid === null) {
|
||||
throw new \moodle_exception('notenoughrandomquestions', 'quiz');
|
||||
}
|
||||
|
||||
}
|
||||
return $newqusetionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version information for a question to show in the version selection dropdown.
|
||||
*
|
||||
* @param int $questionid
|
||||
* @param int $slotid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_version_info($questionid, $slotid): array {
|
||||
global $DB;
|
||||
$versiondata = [];
|
||||
$versionsoptions = self::get_version_options($questionid);
|
||||
$latestversion = reset($versionsoptions);
|
||||
// Object for using the latest version.
|
||||
$alwaysuselatest = new \stdClass();
|
||||
$alwaysuselatest->versionid = 0;
|
||||
$alwaysuselatest->version = 0;
|
||||
$alwaysuselatest->versionvalue = get_string('alwayslatest', 'quiz');
|
||||
array_unshift($versionsoptions, $alwaysuselatest);
|
||||
$referencedata = $DB->get_record('question_references', ['itemid' => $slotid]);
|
||||
if (!isset($referencedata->version) || ($referencedata->version === null)) {
|
||||
$currentversion = 0;
|
||||
} else {
|
||||
$currentversion = $referencedata->version;
|
||||
}
|
||||
|
||||
foreach ($versionsoptions as $versionsoption) {
|
||||
$versionsoption->selected = false;
|
||||
if ($versionsoption->version === $currentversion) {
|
||||
$versionsoption->selected = true;
|
||||
}
|
||||
if (!isset($versionsoption->versionvalue)) {
|
||||
if ($versionsoption->version === $latestversion->version) {
|
||||
$versionsoption->versionvalue = get_string('questionversionlatest', 'quiz', $versionsoption->version);
|
||||
} else {
|
||||
$versionsoption->versionvalue = get_string('questionversion', 'quiz', $versionsoption->version);
|
||||
}
|
||||
}
|
||||
|
||||
$versiondata[] = $versionsoption;
|
||||
}
|
||||
return $versiondata;
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ class question_name_text_column extends question_name_column {
|
|||
$fields = parent::get_required_fields();
|
||||
$fields[] = 'q.questiontext';
|
||||
$fields[] = 'q.questiontextformat';
|
||||
$fields[] = 'q.idnumber';
|
||||
$fields[] = 'qbe.idnumber';
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@
|
|||
*/
|
||||
|
||||
namespace mod_quiz\question;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/mod/quiz/attemptlib.php');
|
||||
|
||||
/**
|
||||
* A {@link qubaid_condition} representing all the attempts by one user at a given quiz.
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
*/
|
||||
|
||||
namespace mod_quiz;
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
|
@ -126,6 +128,15 @@ class structure {
|
|||
return $this->questions[$this->slotsinorder[$slotnumber]->questionid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information about the question name in a given slot.
|
||||
* @param int $slotnumber the index of the slot in question.
|
||||
* @return \stdClass the data from the questions table, augmented with
|
||||
*/
|
||||
public function get_question_name_in_slot($slotnumber) {
|
||||
return $this->questions[$this->slotsinorder[$slotnumber]->name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayed question number (or 'i') for a given slot.
|
||||
* @param int $slotnumber the index of the slot in question.
|
||||
|
@ -606,30 +617,29 @@ class structure {
|
|||
public function populate_structure($quiz) {
|
||||
global $DB;
|
||||
|
||||
$slots = $DB->get_records_sql("
|
||||
SELECT slot.id AS slotid, slot.slot, slot.questionid, slot.page, slot.maxmark,
|
||||
slot.requireprevious, q.*, qc.contextid
|
||||
FROM {quiz_slots} slot
|
||||
LEFT JOIN {question} q ON q.id = slot.questionid
|
||||
LEFT JOIN {question_categories} qc ON qc.id = q.category
|
||||
WHERE slot.quizid = ?
|
||||
ORDER BY slot.slot", array($quiz->id));
|
||||
$slots = qbank_helper::get_question_structure($quiz->id);
|
||||
|
||||
$slots = $this->populate_missing_questions($slots);
|
||||
|
||||
$this->questions = array();
|
||||
$this->slotsinorder = array();
|
||||
$this->questions = [];
|
||||
$this->slotsinorder = [];
|
||||
foreach ($slots as $slotdata) {
|
||||
$this->questions[$slotdata->questionid] = $slotdata;
|
||||
|
||||
$slot = new \stdClass();
|
||||
$slot->id = $slotdata->slotid;
|
||||
$slot->name = $slotdata->name;
|
||||
$slot->slot = $slotdata->slot;
|
||||
$slot->quizid = $quiz->id;
|
||||
$slot->page = $slotdata->page;
|
||||
$slot->questionid = $slotdata->questionid;
|
||||
$slot->maxmark = $slotdata->maxmark;
|
||||
$slot->requireprevious = $slotdata->requireprevious;
|
||||
$slot->qtype = $slotdata->qtype;
|
||||
$slot->length = $slotdata->length;
|
||||
$slot->category = $slotdata->category;
|
||||
$slot->questionbankentryid = $slotdata->questionbankentryid ?? null;
|
||||
$slot->version = $slotdata->version ?? null;
|
||||
|
||||
$this->slotsinorder[$slot->slot] = $slot;
|
||||
}
|
||||
|
@ -646,21 +656,30 @@ class structure {
|
|||
* @return \stdClass[] updated $slots array.
|
||||
*/
|
||||
protected function populate_missing_questions($slots) {
|
||||
// Address missing question types.
|
||||
global $DB;
|
||||
// Address missing/random question types.
|
||||
foreach ($slots as $slot) {
|
||||
if ($slot->qtype === null) {
|
||||
// If the questiontype is missing change the question type.
|
||||
$slot->id = $slot->questionid;
|
||||
$slot->category = 0;
|
||||
$slot->qtype = 'missingtype';
|
||||
$slot->name = get_string('missingquestion', 'quiz');
|
||||
$slot->slot = $slot->slot;
|
||||
$slot->maxmark = 0;
|
||||
$slot->requireprevious = 0;
|
||||
$slot->questiontext = ' ';
|
||||
$slot->questiontextformat = FORMAT_HTML;
|
||||
$slot->length = 1;
|
||||
|
||||
// Check if the question is random.
|
||||
if ($setreference = $DB->get_record('question_set_references', ['itemid' => $slot->slotid])) {
|
||||
$filtercondition = json_decode($setreference->filtercondition);
|
||||
$slot->id = $slot->slotid;
|
||||
$slot->category = $filtercondition->questioncategoryid;
|
||||
$slot->qtype = 'random';
|
||||
$slot->name = get_string('random', 'quiz');
|
||||
$slot->length = 1;
|
||||
} else {
|
||||
// If the questiontype is missing change the question type.
|
||||
$slot->id = $slot->questionid;
|
||||
$slot->category = 0;
|
||||
$slot->qtype = 'missingtype';
|
||||
$slot->name = get_string('missingquestion', 'quiz');
|
||||
$slot->maxmark = 0;
|
||||
$slot->requireprevious = 0;
|
||||
$slot->questiontext = ' ';
|
||||
$slot->questiontextformat = FORMAT_HTML;
|
||||
$slot->length = 1;
|
||||
}
|
||||
} else if (!\question_bank::qtype_exists($slot->qtype)) {
|
||||
$slot->qtype = 'missingtype';
|
||||
}
|
||||
|
@ -936,7 +955,18 @@ class structure {
|
|||
$maxslot = $DB->get_field_sql('SELECT MAX(slot) FROM {quiz_slots} WHERE quizid = ?', array($this->get_quizid()));
|
||||
|
||||
$trans = $DB->start_delegated_transaction();
|
||||
$DB->delete_records('quiz_slot_tags', array('slotid' => $slot->id));
|
||||
// Delete the reference if its a question.
|
||||
$questionreference = $DB->get_record('question_references',
|
||||
['component' => 'mod_quiz', 'questionarea' => 'slot', 'itemid' => $slot->id]);
|
||||
if ($questionreference) {
|
||||
$DB->delete_records('question_references', ['id' => $questionreference->id]);
|
||||
}
|
||||
// Delete the set reference if its a random question.
|
||||
$questionsetreference = $DB->get_record('question_set_references',
|
||||
['component' => 'mod_quiz', 'questionarea' => 'slot', 'itemid' => $slot->id]);
|
||||
if ($questionsetreference) {
|
||||
$DB->delete_records('question_set_references', ['id' => $questionsetreference->id]);
|
||||
}
|
||||
$DB->delete_records('quiz_slots', array('id' => $slot->id));
|
||||
for ($i = $slot->slot + 1; $i <= $maxslot; $i++) {
|
||||
$DB->set_field('quiz_slots', 'slot', $i - 1,
|
||||
|
@ -946,12 +976,6 @@ class structure {
|
|||
unset($this->slotsinorder[$i]);
|
||||
}
|
||||
|
||||
$qtype = $DB->get_field('question', 'qtype', array('id' => $slot->questionid));
|
||||
if ($qtype === 'random') {
|
||||
// This function automatically checks if the question is in use, and won't delete if it is.
|
||||
question_delete_question($slot->questionid);
|
||||
}
|
||||
|
||||
quiz_update_section_firstslots($this->get_quizid(), -1, $slotnumber);
|
||||
foreach ($this->sections as $key => $section) {
|
||||
if ($section->firstslot > $slotnumber) {
|
||||
|
@ -960,7 +984,7 @@ class structure {
|
|||
}
|
||||
$this->populate_slots_with_sections();
|
||||
$this->populate_question_numbers();
|
||||
unset($this->questions[$slot->questionid]);
|
||||
$this->unset_question($slot->id);
|
||||
|
||||
$this->refresh_page_numbers_and_update_db();
|
||||
|
||||
|
@ -978,6 +1002,19 @@ class structure {
|
|||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the question object after deletion.
|
||||
*
|
||||
* @param int $slotid
|
||||
*/
|
||||
public function unset_question($slotid) {
|
||||
foreach ($this->questions as $key => $question) {
|
||||
if ($question->slotid === $slotid) {
|
||||
unset($this->questions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the max mark for a slot.
|
||||
*
|
||||
|
@ -1203,30 +1240,6 @@ class structure {
|
|||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up this class with the slot tags for each of the slots.
|
||||
*/
|
||||
protected function populate_slot_tags() {
|
||||
$slotids = array_column($this->slotsinorder, 'id');
|
||||
$this->slottags = quiz_retrieve_tags_for_slot_ids($slotids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of slot tags for the given slot id.
|
||||
*
|
||||
* @param int $slotid The id for the slot
|
||||
* @return \stdClass[] The list of slot tag records
|
||||
*/
|
||||
public function get_slot_tags_for_slot_id($slotid) {
|
||||
if (!$this->hasloadedtags) {
|
||||
// Lazy load the tags just in case they are never required.
|
||||
$this->populate_slot_tags();
|
||||
$this->hasloadedtags = true;
|
||||
}
|
||||
|
||||
return isset($this->slottags[$slotid]) ? $this->slottags[$slotid] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current user can add random questions to the quiz or not.
|
||||
* It is only possible to add a random question if the user has the moodle/question:useall capability
|
||||
|
@ -1237,7 +1250,7 @@ class structure {
|
|||
public function can_add_random_questions() {
|
||||
if ($this->canaddrandom === null) {
|
||||
$quizcontext = $this->quizobj->get_context();
|
||||
$relatedcontexts = new \question_edit_contexts($quizcontext);
|
||||
$relatedcontexts = new \core_question\local\bank\question_edit_contexts($quizcontext);
|
||||
$usablecontexts = $relatedcontexts->having_cap('moodle/question:useall');
|
||||
|
||||
$this->canaddrandom = !empty($usablecontexts);
|
||||
|
@ -1245,4 +1258,21 @@ class structure {
|
|||
|
||||
return $this->canaddrandom;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the list of slot tags for the given slot id.
|
||||
*
|
||||
* @param int $slotid The id for the slot
|
||||
* @return \stdClass[] The list of slot tag records
|
||||
* @deprecated since Moodle 4.0 MDL-71573
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
public function get_slot_tags_for_slot_id($slotid) {
|
||||
debugging('Function get_slot_tags_for_slot_id() has been deprecated and the structure
|
||||
for this method have been moved to filtercondition in question_set_reference table, please
|
||||
use the new structure instead.', DEBUG_DEVELOPER);
|
||||
// All the associated code for this method have been removed to get rid of accidental call or errors.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue