You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
621 lines
23 KiB
621 lines
23 KiB
2 years ago
|
<?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/>.
|
||
|
|
||
|
/**
|
||
|
* More object oriented wrappers around parts of the Moodle question bank.
|
||
|
*
|
||
|
* In due course, I expect that the question bank will be converted to a
|
||
|
* fully object oriented structure, at which point this file can be a
|
||
|
* starting point.
|
||
|
*
|
||
|
* @package moodlecore
|
||
|
* @subpackage questionbank
|
||
|
* @copyright 2009 The Open University
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
|
||
|
|
||
|
defined('MOODLE_INTERNAL') || die();
|
||
|
|
||
|
require_once(__DIR__ . '/../type/questiontypebase.php');
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This static class provides access to the other question bank.
|
||
|
*
|
||
|
* It provides functions for managing question types and question definitions.
|
||
|
*
|
||
|
* @copyright 2009 The Open University
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
abstract class question_bank {
|
||
|
// TODO: This limit can be deleted if someday we move all TEXTS to BIG ones. MDL-19603
|
||
|
const MAX_SUMMARY_LENGTH = 32000;
|
||
|
|
||
|
/** @var array question type name => question_type subclass. */
|
||
|
private static $questiontypes = array();
|
||
|
|
||
|
/** @var array question type name => 1. Records which question definitions have been loaded. */
|
||
|
private static $loadedqdefs = array();
|
||
|
|
||
|
/** @var boolean nasty hack to allow unit tests to call {@link load_question()}. */
|
||
|
private static $testmode = false;
|
||
|
private static $testdata = array();
|
||
|
|
||
|
private static $questionconfig = null;
|
||
|
|
||
|
/**
|
||
|
* @var array string => string The standard set of grade options (fractions)
|
||
|
* to use when editing questions, in the range 0 to 1 inclusive. Array keys
|
||
|
* are string becuase: a) we want grades to exactly 7 d.p., and b. you can't
|
||
|
* have float array keys in PHP.
|
||
|
* Initialised by {@link ensure_grade_options_initialised()}.
|
||
|
*/
|
||
|
private static $fractionoptions = null;
|
||
|
/** @var array string => string The full standard set of (fractions) -1 to 1 inclusive. */
|
||
|
private static $fractionoptionsfull = null;
|
||
|
|
||
|
/**
|
||
|
* @param string $qtypename a question type name, e.g. 'multichoice'.
|
||
|
* @return bool whether that question type is installed in this Moodle.
|
||
|
*/
|
||
|
public static function is_qtype_installed($qtypename) {
|
||
|
$plugindir = core_component::get_plugin_directory('qtype', $qtypename);
|
||
|
return $plugindir && is_readable($plugindir . '/questiontype.php');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the question type class for a particular question type.
|
||
|
* @param string $qtypename the question type name. For example 'multichoice' or 'shortanswer'.
|
||
|
* @param bool $mustexist if false, the missing question type is returned when
|
||
|
* the requested question type is not installed.
|
||
|
* @return question_type the corresponding question type class.
|
||
|
*/
|
||
|
public static function get_qtype($qtypename, $mustexist = true) {
|
||
|
global $CFG;
|
||
|
if (isset(self::$questiontypes[$qtypename])) {
|
||
|
return self::$questiontypes[$qtypename];
|
||
|
}
|
||
|
$file = core_component::get_plugin_directory('qtype', $qtypename) . '/questiontype.php';
|
||
|
if (!is_readable($file)) {
|
||
|
if ($mustexist || $qtypename == 'missingtype') {
|
||
|
throw new coding_exception('Unknown question type ' . $qtypename);
|
||
|
} else {
|
||
|
return self::get_qtype('missingtype');
|
||
|
}
|
||
|
}
|
||
|
include_once($file);
|
||
|
$class = 'qtype_' . $qtypename;
|
||
|
if (!class_exists($class)) {
|
||
|
throw new coding_exception("Class {$class} must be defined in {$file}.");
|
||
|
}
|
||
|
self::$questiontypes[$qtypename] = new $class();
|
||
|
return self::$questiontypes[$qtypename];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load the question configuration data from config_plugins.
|
||
|
* @return object get_config('question') with caching.
|
||
|
*/
|
||
|
public static function get_config() {
|
||
|
if (is_null(self::$questionconfig)) {
|
||
|
self::$questionconfig = get_config('question');
|
||
|
}
|
||
|
return self::$questionconfig;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $qtypename the internal name of a question type. For example multichoice.
|
||
|
* @return bool whether users are allowed to create questions of this type.
|
||
|
*/
|
||
|
public static function qtype_enabled($qtypename) {
|
||
|
$config = self::get_config();
|
||
|
$enabledvar = $qtypename . '_disabled';
|
||
|
return self::qtype_exists($qtypename) && empty($config->$enabledvar) &&
|
||
|
self::get_qtype($qtypename)->menu_name() != '';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $qtypename the internal name of a question type. For example multichoice.
|
||
|
* @return bool whether this question type exists.
|
||
|
*/
|
||
|
public static function qtype_exists($qtypename) {
|
||
|
return array_key_exists($qtypename, core_component::get_plugin_list('qtype'));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $qtypename the internal name of a question type, for example multichoice.
|
||
|
* @return string the human_readable name of this question type, from the language pack.
|
||
|
*/
|
||
|
public static function get_qtype_name($qtypename) {
|
||
|
return self::get_qtype($qtypename)->local_name();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array all the installed question types.
|
||
|
*/
|
||
|
public static function get_all_qtypes() {
|
||
|
$qtypes = array();
|
||
|
foreach (core_component::get_plugin_list('qtype') as $plugin => $notused) {
|
||
|
try {
|
||
|
$qtypes[$plugin] = self::get_qtype($plugin);
|
||
|
} catch (coding_exception $e) {
|
||
|
// Catching coding_exceptions here means that incompatible
|
||
|
// question types do not cause the rest of Moodle to break.
|
||
|
}
|
||
|
}
|
||
|
return $qtypes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sort an array of question types according to the order the admin set up,
|
||
|
* and then alphabetically for the rest.
|
||
|
* @param array qtype->name() => qtype->local_name().
|
||
|
* @return array sorted array.
|
||
|
*/
|
||
|
public static function sort_qtype_array($qtypes, $config = null) {
|
||
|
if (is_null($config)) {
|
||
|
$config = self::get_config();
|
||
|
}
|
||
|
|
||
|
$sortorder = array();
|
||
|
$otherqtypes = array();
|
||
|
foreach ($qtypes as $name => $localname) {
|
||
|
$sortvar = $name . '_sortorder';
|
||
|
if (isset($config->$sortvar)) {
|
||
|
$sortorder[$config->$sortvar] = $name;
|
||
|
} else {
|
||
|
$otherqtypes[$name] = $localname;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ksort($sortorder);
|
||
|
core_collator::asort($otherqtypes);
|
||
|
|
||
|
$sortedqtypes = array();
|
||
|
foreach ($sortorder as $name) {
|
||
|
$sortedqtypes[$name] = $qtypes[$name];
|
||
|
}
|
||
|
foreach ($otherqtypes as $name => $notused) {
|
||
|
$sortedqtypes[$name] = $qtypes[$name];
|
||
|
}
|
||
|
return $sortedqtypes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array all the question types that users are allowed to create,
|
||
|
* sorted into the preferred order set on the admin screen.
|
||
|
*/
|
||
|
public static function get_creatable_qtypes() {
|
||
|
$config = self::get_config();
|
||
|
$allqtypes = self::get_all_qtypes();
|
||
|
|
||
|
$qtypenames = array();
|
||
|
foreach ($allqtypes as $name => $qtype) {
|
||
|
if (self::qtype_enabled($name)) {
|
||
|
$qtypenames[$name] = $qtype->local_name();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$qtypenames = self::sort_qtype_array($qtypenames);
|
||
|
|
||
|
$creatableqtypes = array();
|
||
|
foreach ($qtypenames as $name => $notused) {
|
||
|
$creatableqtypes[$name] = $allqtypes[$name];
|
||
|
}
|
||
|
return $creatableqtypes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load the question definition class(es) belonging to a question type. That is,
|
||
|
* include_once('/question/type/' . $qtypename . '/question.php'), with a bit
|
||
|
* of checking.
|
||
|
* @param string $qtypename the question type name. For example 'multichoice' or 'shortanswer'.
|
||
|
*/
|
||
|
public static function load_question_definition_classes($qtypename) {
|
||
|
global $CFG;
|
||
|
if (isset(self::$loadedqdefs[$qtypename])) {
|
||
|
return;
|
||
|
}
|
||
|
$file = $CFG->dirroot . '/question/type/' . $qtypename . '/question.php';
|
||
|
if (!is_readable($file)) {
|
||
|
throw new coding_exception('Unknown question type (no definition) ' . $qtypename);
|
||
|
}
|
||
|
include_once($file);
|
||
|
self::$loadedqdefs[$qtypename] = 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method needs to be called whenever a question is edited.
|
||
|
*/
|
||
|
public static function notify_question_edited($questionid) {
|
||
|
question_finder::get_instance()->uncache_question($questionid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load a question definition data from the database. The data will be
|
||
|
* returned as a plain stdClass object.
|
||
|
* @param int $questionid the id of the question to load.
|
||
|
* @return object question definition loaded from the database.
|
||
|
*/
|
||
|
public static function load_question_data($questionid) {
|
||
|
return question_finder::get_instance()->load_question_data($questionid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load a question definition from the database. The object returned
|
||
|
* will actually be of an appropriate {@link question_definition} subclass.
|
||
|
* @param int $questionid the id of the question to load.
|
||
|
* @param bool $allowshuffle if false, then any shuffle option on the selected
|
||
|
* quetsion is disabled.
|
||
|
* @return question_definition loaded from the database.
|
||
|
*/
|
||
|
public static function load_question($questionid, $allowshuffle = true) {
|
||
|
global $DB;
|
||
|
|
||
|
if (self::$testmode) {
|
||
|
// Evil, test code in production, but no way round it.
|
||
|
return self::return_test_question_data($questionid);
|
||
|
}
|
||
|
|
||
|
$questiondata = self::load_question_data($questionid);
|
||
|
|
||
|
if (!$allowshuffle) {
|
||
|
$questiondata->options->shuffleanswers = false;
|
||
|
}
|
||
|
return self::make_question($questiondata);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert the question information loaded with {@link get_question_options()}
|
||
|
* to a question_definintion object.
|
||
|
* @param object $questiondata raw data loaded from the database.
|
||
|
* @return question_definition loaded from the database.
|
||
|
*/
|
||
|
public static function make_question($questiondata) {
|
||
|
return self::get_qtype($questiondata->qtype, false)->make_question($questiondata, false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return question_finder a question finder.
|
||
|
*/
|
||
|
public static function get_finder() {
|
||
|
return question_finder::get_instance();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only to be called from unit tests. Allows {@link load_test_data()} to be used.
|
||
|
*/
|
||
|
public static function start_unit_test() {
|
||
|
self::$testmode = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only to be called from unit tests. Allows {@link load_test_data()} to be used.
|
||
|
*/
|
||
|
public static function end_unit_test() {
|
||
|
self::$testmode = false;
|
||
|
self::$testdata = array();
|
||
|
}
|
||
|
|
||
|
private static function return_test_question_data($questionid) {
|
||
|
if (!isset(self::$testdata[$questionid])) {
|
||
|
throw new coding_exception('question_bank::return_test_data(' . $questionid .
|
||
|
') called, but no matching question has been loaded by load_test_data.');
|
||
|
}
|
||
|
return self::$testdata[$questionid];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To be used for unit testing only. Will throw an exception if
|
||
|
* {@link start_unit_test()} has not been called first.
|
||
|
* @param object $questiondata a question data object to put in the test data store.
|
||
|
*/
|
||
|
public static function load_test_question_data(question_definition $question) {
|
||
|
if (!self::$testmode) {
|
||
|
throw new coding_exception('question_bank::load_test_data called when ' .
|
||
|
'not in test mode.');
|
||
|
}
|
||
|
self::$testdata[$question->id] = $question;
|
||
|
}
|
||
|
|
||
|
protected static function ensure_fraction_options_initialised() {
|
||
|
if (!is_null(self::$fractionoptions)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// define basic array of grades. This list comprises all fractions of the form:
|
||
|
// a. p/q for q <= 6, 0 <= p <= q
|
||
|
// b. p/10 for 0 <= p <= 10
|
||
|
// c. 1/q for 1 <= q <= 10
|
||
|
// d. 1/20
|
||
|
$rawfractions = array(
|
||
|
0.9000000,
|
||
|
0.8333333,
|
||
|
0.8000000,
|
||
|
0.7500000,
|
||
|
0.7000000,
|
||
|
0.6666667,
|
||
|
0.6000000,
|
||
|
0.5000000,
|
||
|
0.4000000,
|
||
|
0.3333333,
|
||
|
0.3000000,
|
||
|
0.2500000,
|
||
|
0.2000000,
|
||
|
0.1666667,
|
||
|
0.1428571,
|
||
|
0.1250000,
|
||
|
0.1111111,
|
||
|
0.1000000,
|
||
|
0.0500000,
|
||
|
);
|
||
|
|
||
|
// Put the None option at the top.
|
||
|
self::$fractionoptions = array(
|
||
|
'0.0' => get_string('none'),
|
||
|
'1.0' => '100%',
|
||
|
);
|
||
|
self::$fractionoptionsfull = array(
|
||
|
'0.0' => get_string('none'),
|
||
|
'1.0' => '100%',
|
||
|
);
|
||
|
|
||
|
// The the positive grades in descending order.
|
||
|
foreach ($rawfractions as $fraction) {
|
||
|
$percentage = format_float(100 * $fraction, 5, true, true) . '%';
|
||
|
self::$fractionoptions["{$fraction}"] = $percentage;
|
||
|
self::$fractionoptionsfull["{$fraction}"] = $percentage;
|
||
|
}
|
||
|
|
||
|
// The the negative grades in descending order.
|
||
|
foreach (array_reverse($rawfractions) as $fraction) {
|
||
|
self::$fractionoptionsfull['' . (-$fraction)] =
|
||
|
format_float(-100 * $fraction, 5, true, true) . '%';
|
||
|
}
|
||
|
|
||
|
self::$fractionoptionsfull['-1.0'] = '-100%';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array string => string The standard set of grade options (fractions)
|
||
|
* to use when editing questions, in the range 0 to 1 inclusive. Array keys
|
||
|
* are string becuase: a) we want grades to exactly 7 d.p., and b. you can't
|
||
|
* have float array keys in PHP.
|
||
|
* Initialised by {@link ensure_grade_options_initialised()}.
|
||
|
*/
|
||
|
public static function fraction_options() {
|
||
|
self::ensure_fraction_options_initialised();
|
||
|
return self::$fractionoptions;
|
||
|
}
|
||
|
|
||
|
/** @return array string => string The full standard set of (fractions) -1 to 1 inclusive. */
|
||
|
public static function fraction_options_full() {
|
||
|
self::ensure_fraction_options_initialised();
|
||
|
return self::$fractionoptionsfull;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a list of the different question types present in the given categories.
|
||
|
*
|
||
|
* @param array $categories a list of category ids
|
||
|
* @return array the list of question types in the categories
|
||
|
* @since Moodle 3.1
|
||
|
*/
|
||
|
public static function get_all_question_types_in_categories($categories) {
|
||
|
global $DB;
|
||
|
|
||
|
list($categorysql, $params) = $DB->get_in_or_equal($categories);
|
||
|
$sql = "SELECT DISTINCT q.qtype
|
||
|
FROM {question} q
|
||
|
WHERE q.category $categorysql";
|
||
|
|
||
|
$qtypes = $DB->get_fieldset_sql($sql, $params);
|
||
|
return $qtypes;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Class for loading questions according to various criteria.
|
||
|
*
|
||
|
* @copyright 2009 The Open University
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
class question_finder implements cache_data_source {
|
||
|
/** @var question_finder the singleton instance of this class. */
|
||
|
protected static $questionfinder = null;
|
||
|
|
||
|
/**
|
||
|
* @return question_finder a question finder.
|
||
|
*/
|
||
|
public static function get_instance() {
|
||
|
if (is_null(self::$questionfinder)) {
|
||
|
self::$questionfinder = new question_finder();
|
||
|
}
|
||
|
return self::$questionfinder;
|
||
|
}
|
||
|
|
||
|
/* See cache_data_source::get_instance_for_cache. */
|
||
|
public static function get_instance_for_cache(cache_definition $definition) {
|
||
|
return self::get_instance();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return get the question definition cache we are using.
|
||
|
*/
|
||
|
protected function get_data_cache() {
|
||
|
// Do not double cache here because it may break cache resetting.
|
||
|
return cache::make('core', 'questiondata');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method needs to be called whenever a question is edited.
|
||
|
*/
|
||
|
public function uncache_question($questionid) {
|
||
|
$this->get_data_cache()->delete($questionid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load a question definition data from the database. The data will be
|
||
|
* returned as a plain stdClass object.
|
||
|
* @param int $questionid the id of the question to load.
|
||
|
* @return object question definition loaded from the database.
|
||
|
*/
|
||
|
public function load_question_data($questionid) {
|
||
|
return $this->get_data_cache()->get($questionid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the ids of all the questions in a list of categories.
|
||
|
* @param array $categoryids either a categoryid, or a comma-separated list
|
||
|
* category ids, or an array of them.
|
||
|
* @param string $extraconditions extra conditions to AND with the rest of
|
||
|
* the where clause. Must use named parameters.
|
||
|
* @param array $extraparams any parameters used by $extraconditions.
|
||
|
* @return array questionid => questionid.
|
||
|
*/
|
||
|
public function get_questions_from_categories($categoryids, $extraconditions,
|
||
|
$extraparams = array()) {
|
||
|
global $DB;
|
||
|
|
||
|
list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc');
|
||
|
|
||
|
if ($extraconditions) {
|
||
|
$extraconditions = ' AND (' . $extraconditions . ')';
|
||
|
}
|
||
|
|
||
|
return $DB->get_records_select_menu('question',
|
||
|
"category {$qcsql}
|
||
|
AND parent = 0
|
||
|
AND hidden = 0
|
||
|
{$extraconditions}", $qcparams + $extraparams, '', 'id,id AS id2');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the ids of all the questions in a list of categories, with the number
|
||
|
* of times they have already been used in a given set of usages.
|
||
|
*
|
||
|
* The result array is returned in order of increasing (count previous uses).
|
||
|
*
|
||
|
* @param array $categoryids an array question_category ids.
|
||
|
* @param qubaid_condition $qubaids which question_usages to count previous uses from.
|
||
|
* @param string $extraconditions extra conditions to AND with the rest of
|
||
|
* the where clause. Must use named parameters.
|
||
|
* @param array $extraparams any parameters used by $extraconditions.
|
||
|
* @return array questionid => count of number of previous uses.
|
||
|
*/
|
||
|
public function get_questions_from_categories_with_usage_counts($categoryids,
|
||
|
qubaid_condition $qubaids, $extraconditions = '', $extraparams = array()) {
|
||
|
return $this->get_questions_from_categories_and_tags_with_usage_counts(
|
||
|
$categoryids, $qubaids, $extraconditions, $extraparams);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the ids of all the questions in a list of categories that have ALL the provided tags,
|
||
|
* with the number of times they have already been used in a given set of usages.
|
||
|
*
|
||
|
* The result array is returned in order of increasing (count previous uses).
|
||
|
*
|
||
|
* @param array $categoryids an array of question_category ids.
|
||
|
* @param qubaid_condition $qubaids which question_usages to count previous uses from.
|
||
|
* @param string $extraconditions extra conditions to AND with the rest of
|
||
|
* the where clause. Must use named parameters.
|
||
|
* @param array $extraparams any parameters used by $extraconditions.
|
||
|
* @param array $tagids an array of tag ids
|
||
|
* @return array questionid => count of number of previous uses.
|
||
|
*/
|
||
|
public function get_questions_from_categories_and_tags_with_usage_counts($categoryids,
|
||
|
qubaid_condition $qubaids, $extraconditions = '', $extraparams = array(), $tagids = array()) {
|
||
|
global $DB;
|
||
|
|
||
|
list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc');
|
||
|
|
||
|
$select = "q.id, (SELECT COUNT(1)
|
||
|
FROM " . $qubaids->from_question_attempts('qa') . "
|
||
|
WHERE qa.questionid = q.id AND " . $qubaids->where() . "
|
||
|
) AS previous_attempts";
|
||
|
$from = "{question} q";
|
||
|
$where = "q.category {$qcsql}
|
||
|
AND q.parent = 0
|
||
|
AND q.hidden = 0";
|
||
|
$params = $qcparams;
|
||
|
|
||
|
if (!empty($tagids)) {
|
||
|
// We treat each additional tag as an AND condition rather than
|
||
|
// an OR condition.
|
||
|
//
|
||
|
// For example, if the user filters by the tags "foo" and "bar" then
|
||
|
// we reduce the question list to questions that are tagged with both
|
||
|
// "foo" AND "bar". Any question that does not have ALL of the specified
|
||
|
// tags will be omitted.
|
||
|
list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED, 'ti');
|
||
|
$tagparams['tagcount'] = count($tagids);
|
||
|
$tagparams['questionitemtype'] = 'question';
|
||
|
$tagparams['questioncomponent'] = 'core_question';
|
||
|
$where .= " AND q.id IN (SELECT ti.itemid
|
||
|
FROM {tag_instance} ti
|
||
|
WHERE ti.itemtype = :questionitemtype
|
||
|
AND ti.component = :questioncomponent
|
||
|
AND ti.tagid {$tagsql}
|
||
|
GROUP BY ti.itemid
|
||
|
HAVING COUNT(itemid) = :tagcount)";
|
||
|
$params += $tagparams;
|
||
|
}
|
||
|
|
||
|
if ($extraconditions) {
|
||
|
$extraconditions = ' AND (' . $extraconditions . ')';
|
||
|
}
|
||
|
|
||
|
return $DB->get_records_sql_menu("SELECT $select
|
||
|
FROM $from
|
||
|
WHERE $where $extraconditions
|
||
|
ORDER BY previous_attempts",
|
||
|
$qubaids->from_where_params() + $params + $extraparams);
|
||
|
}
|
||
|
|
||
|
/* See cache_data_source::load_for_cache. */
|
||
|
public function load_for_cache($questionid) {
|
||
|
global $DB;
|
||
|
$questiondata = $DB->get_record_sql('
|
||
|
SELECT q.*, qc.contextid
|
||
|
FROM {question} q
|
||
|
JOIN {question_categories} qc ON q.category = qc.id
|
||
|
WHERE q.id = :id', array('id' => $questionid), MUST_EXIST);
|
||
|
get_question_options($questiondata);
|
||
|
return $questiondata;
|
||
|
}
|
||
|
|
||
|
/* See cache_data_source::load_many_for_cache. */
|
||
|
public function load_many_for_cache(array $questionids) {
|
||
|
global $DB;
|
||
|
list($idcondition, $params) = $DB->get_in_or_equal($questionids);
|
||
|
$questiondata = $DB->get_records_sql('
|
||
|
SELECT q.*, qc.contextid
|
||
|
FROM {question} q
|
||
|
JOIN {question_categories} qc ON q.category = qc.id
|
||
|
WHERE q.id ' . $idcondition, $params);
|
||
|
|
||
|
foreach ($questionids as $id) {
|
||
|
if (!array_key_exists($id, $questionids)) {
|
||
|
throw new dml_missing_record_exception('question', '', array('id' => $id));
|
||
|
}
|
||
|
get_question_options($questiondata[$id]);
|
||
|
}
|
||
|
return $questiondata;
|
||
|
}
|
||
|
}
|