diff --git a/.github/workflows/moodle-plugin-ci.yml b/.github/workflows/moodle-plugin-ci.yml
index 35748095..ad89f1dc 100644
--- a/.github/workflows/moodle-plugin-ci.yml
+++ b/.github/workflows/moodle-plugin-ci.yml
@@ -38,9 +38,9 @@ jobs:
include:
- {php: '7.3', moodle-branch: MOODLE_400_STABLE, database: pgsql}
- {php: '7.4', moodle-branch: MOODLE_401_STABLE, database: mariadb}
- - {php: '8.1', moodle-branch: MOODLE_402_STABLE, database: pgsql}
+ - {php: '8.0', moodle-branch: MOODLE_402_STABLE, database: pgsql}
- {php: '8.2', moodle-branch: MOODLE_403_STABLE, database: mariadb}
- - {php: '8.0', moodle-branch: master, database: pgsql}
+ - {php: '8.1', moodle-branch: main, database: pgsql}
steps:
- name: Check out repository code
@@ -73,10 +73,12 @@ jobs:
- name: PHP Lint
if: ${{ always() }}
+ continue-on-error: true # This step will show errors but will not fail
run: moodle-plugin-ci phplint
- name: PHP Copy/Paste Detector
if: ${{ always() }}
+ continue-on-error: true # This step will show errors but will not fail
run: moodle-plugin-ci phpcpd
- name: PHP Mess Detector
diff --git a/amd/build/question_nav_tabs.min.js b/amd/build/question_nav_tabs.min.js
index 74f98e31..77d8e1d5 100644
--- a/amd/build/question_nav_tabs.min.js
+++ b/amd/build/question_nav_tabs.min.js
@@ -1,11 +1,10 @@
-define("mod_studentquiz/question_nav_tabs",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
+define("mod_studentquiz/question_nav_tabs",["exports","core_user/repository"],(function(_exports,UserRepository){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,UserRepository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
/**
* Javascript for question-nav-tabs.
*
* @module mod_studentquiz/question_nav_tabs
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-const updateActiveTab=e=>M.util.set_user_preference("mod_studentquiz_question_active_tab",e.target.dataset.tabId);_exports.init=()=>{document.querySelectorAll(".question-nav-tabs > .nav-tabs > .nav-item.nav-link").forEach((tab=>{tab.addEventListener("click",updateActiveTab)}))}}));
+ */(UserRepository);const updateActiveTab=e=>{void 0!==UserRepository.setUserPreference?UserRepository.setUserPreference("mod_studentquiz_question_active_tab",e.target.dataset.tabId):M.util.set_user_preference("mod_studentquiz_question_active_tab",e.target.dataset.tabId)};_exports.init=()=>{document.querySelectorAll(".question-nav-tabs > .nav-tabs > .nav-item.nav-link").forEach((tab=>{tab.addEventListener("click",updateActiveTab)}))}}));
//# sourceMappingURL=question_nav_tabs.min.js.map
\ No newline at end of file
diff --git a/amd/build/question_nav_tabs.min.js.map b/amd/build/question_nav_tabs.min.js.map
index c24f4f34..4882b442 100644
--- a/amd/build/question_nav_tabs.min.js.map
+++ b/amd/build/question_nav_tabs.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"question_nav_tabs.min.js","sources":["../src/question_nav_tabs.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for question-nav-tabs.\n *\n * @module mod_studentquiz/question_nav_tabs\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Update the current active tab to user preferences.\n *\n * @private\n * @param {Object} e Event\n * @return {Promise} The promise object.\n */\nconst updateActiveTab = (e) => {\n return M.util.set_user_preference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);\n};\n\n/**\n * Init the question-nav-tabs.\n *\n */\nexport const init = () => {\n let tabs = document.querySelectorAll('.question-nav-tabs > .nav-tabs > .nav-item.nav-link');\n\n tabs.forEach((tab) => {\n tab.addEventListener('click', updateActiveTab);\n });\n};\n"],"names":["updateActiveTab","e","M","util","set_user_preference","target","dataset","tabId","document","querySelectorAll","forEach","tab","addEventListener"],"mappings":";;;;;;;;MA8BMA,gBAAmBC,GACdC,EAAEC,KAAKC,oBAAoB,sCAAuCH,EAAEI,OAAOC,QAAQC,qBAO1E,KACLC,SAASC,iBAAiB,uDAEhCC,SAASC,MACVA,IAAIC,iBAAiB,QAASZ"}
\ No newline at end of file
+{"version":3,"file":"question_nav_tabs.min.js","sources":["../src/question_nav_tabs.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for question-nav-tabs.\n *\n * @module mod_studentquiz/question_nav_tabs\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as UserRepository from 'core_user/repository';\n\n/**\n * Update the current active tab to user preferences.\n *\n * @private\n * @param {Object} e Event\n */\nconst updateActiveTab = (e) => {\n if (typeof UserRepository.setUserPreference !== 'undefined') {\n UserRepository.setUserPreference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);\n } else {\n M.util.set_user_preference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);\n }\n};\n\n/**\n * Init the question-nav-tabs.\n *\n */\nexport const init = () => {\n let tabs = document.querySelectorAll('.question-nav-tabs > .nav-tabs > .nav-item.nav-link');\n\n tabs.forEach((tab) => {\n tab.addEventListener('click', updateActiveTab);\n });\n};\n"],"names":["updateActiveTab","e","UserRepository","setUserPreference","target","dataset","tabId","M","util","set_user_preference","document","querySelectorAll","forEach","tab","addEventListener"],"mappings":";;;;;;;4BA8BMA,gBAAmBC,SAC2B,IAArCC,eAAeC,kBACtBD,eAAeC,kBAAkB,sCAAuCF,EAAEG,OAAOC,QAAQC,OAEzFC,EAAEC,KAAKC,oBAAoB,sCAAuCR,EAAEG,OAAOC,QAAQC,sBAQvE,KACLI,SAASC,iBAAiB,uDAEhCC,SAASC,MACVA,IAAIC,iBAAiB,QAASd"}
\ No newline at end of file
diff --git a/amd/src/question_nav_tabs.js b/amd/src/question_nav_tabs.js
index 50ab3b68..5fce8ef6 100644
--- a/amd/src/question_nav_tabs.js
+++ b/amd/src/question_nav_tabs.js
@@ -20,16 +20,20 @@
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+import * as UserRepository from 'core_user/repository';
/**
* Update the current active tab to user preferences.
*
* @private
* @param {Object} e Event
- * @return {Promise} The promise object.
*/
const updateActiveTab = (e) => {
- return M.util.set_user_preference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);
+ if (typeof UserRepository.setUserPreference !== 'undefined') {
+ UserRepository.setUserPreference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);
+ } else {
+ M.util.set_user_preference('mod_studentquiz_question_active_tab', e.target.dataset.tabId);
+ }
};
/**
diff --git a/classes/condition/studentquiz_condition.php b/classes/condition/studentquiz_condition.php
index 6266e59d..9435e6b5 100755
--- a/classes/condition/studentquiz_condition.php
+++ b/classes/condition/studentquiz_condition.php
@@ -34,7 +34,7 @@
* @copyright 2017 HSR (http://www.hsr.ch)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class studentquiz_condition extends \core_question\bank\search\condition {
+class studentquiz_condition extends \core_question\local\bank\condition {
/**
* Due to fix_sql_params not accepting repeated use of named params,
@@ -67,6 +67,24 @@ public function __construct($cm, $filterform, $report, $studentquiz) {
$this->init();
}
+ /**
+ * Return title of the condition
+ *
+ * @return string title of the condition
+ */
+ public function get_title() {
+ return get_string('showhidden', 'core_question');
+ }
+
+ /**
+ * Return filter class associated with this condition
+ *
+ * @return string filter class
+ */
+ public function get_filter_class() {
+ return 'qbank_deletequestion/datafilter/filtertypes/hidden';
+ }
+
/** @var stdClass */
protected $cm;
@@ -83,7 +101,7 @@ public function __construct($cm, $filterform, $report, $studentquiz) {
protected $tests;
/** @var array */
- protected $params;
+ protected array $params = [];
/** @var bool */
protected $isfilteractive = false;
diff --git a/classes/condition/studentquiz_condition_pre_43.php b/classes/condition/studentquiz_condition_pre_43.php
new file mode 100644
index 00000000..f3aae8f8
--- /dev/null
+++ b/classes/condition/studentquiz_condition_pre_43.php
@@ -0,0 +1,251 @@
+.
+
+/**
+ * Modify stuff conditionally
+ *
+ * @package mod_studentquiz
+ * @copyright 2017 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_studentquiz\condition;
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/studentquiz/classes/local/db.php');
+use mod_studentquiz\local\db;
+
+/**
+ * Conditionally modify question bank queries.
+ *
+ * @copyright 2017 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class studentquiz_condition_pre_43 extends \core_question\bank\search\condition {
+
+ /**
+ * Due to fix_sql_params not accepting repeated use of named params,
+ * we need to get unique names for params that will be used more than
+ * once...
+ *
+ * init() from parent class duplicated here as we can't call it directly
+ * (private) :-P
+ *
+ * where() overridden with call to init() followed by call to parent
+ * where()...
+ *
+ * params() always returns $this->params, which doesn't change between
+ * calls to get_in_or_equal, so don't need to fix anything there.
+ * Which is fortunate, as there'd be no way to keep where() and params()
+ * in sync.
+ *
+ * @param stdClass $cm
+ * @param stdClass $filterform
+ * @param \mod_studentquiz_report $report
+ * @param stdClass $studentquiz
+ */
+ public function __construct($cm, $filterform, $report, $studentquiz) {
+ $this->cm = $cm;
+ $this->filterform = $filterform;
+ $this->tests = array();
+ $this->params = array();
+ $this->report = $report;
+ $this->studentquiz = $studentquiz;
+ $this->init();
+ }
+
+ /** @var stdClass */
+ protected $cm;
+
+ /** @var stdClass $filterform Search condition depends on filterform */
+ protected $filterform;
+
+ /** @var stdClass */
+ protected $studentquiz;
+
+ /** @var \mod_studentquiz_report */
+ protected $report;
+
+ /** @var array */
+ protected $tests;
+
+ /** @var array */
+ protected $params;
+
+ /** @var bool */
+ protected $isfilteractive = false;
+
+ /**
+ * Whether the filter is active.
+ * @return bool
+ */
+ public function is_filter_active() {
+ return $this->isfilteractive;
+ }
+
+ /**
+ * Initialize.
+ */
+ protected function init() {
+ if ($adddata = $this->filterform->get_data()) {
+
+ $this->tests = array();
+ $this->params = array();
+
+ foreach ($this->filterform->get_fields() as $field) {
+
+ // Validate input.
+ $data = $field->check_data($adddata);
+
+ // If input is valid, at least one filter was activated.
+ if ($data === false) {
+ continue;
+ } else {
+ $this->isfilteractive = true;
+ }
+
+ $sqldata = $field->get_sql_filter($data);
+
+ // Disable filtering by firstname if anonymized.
+ if ($field->_name == 'firstname' && !(mod_studentquiz_check_created_permission($this->cm->id) ||
+ !$this->report->is_anonymized())) {
+ continue;
+ }
+
+ // Disable filtering by firstname if anonymized.
+ if ($field->_name == 'lastname' && !(mod_studentquiz_check_created_permission($this->cm->id) ||
+ !$this->report->is_anonymized())) {
+ continue;
+ }
+
+ // Respect leading and ending ',' for the tagarray as provided by tag_column.php.
+ if ($field->_name == 'tagarray') {
+ foreach ($sqldata[1] as $key => $value) {
+ if (!empty($value)) {
+ $sqldata[1][$key] = "%,$value,%";
+ } else {
+ $sqldata[0] = "$field->_name IS NULL";
+ }
+ }
+ }
+
+ // TODO: cleanup that buggy filter function to remove this!
+ // The user_filter_checkbox class has a buggy get_sql_filter function.
+ if ($field->_name == 'createdby') {
+ $sqldata = array($field->_name . ' = ' . intval($data['value']), array());
+ }
+
+ if (is_array($sqldata)) {
+ $sqldata[0] = str_replace($field->_name, $this->get_sql_field($field->_name)
+ , $sqldata[0]);
+ $sqldata[0] = $this->get_special_sql($sqldata[0], $field->_name);
+ $this->tests[] = '((' . $sqldata[0] . '))';
+ $this->params = array_merge($this->params, $sqldata[1]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Replaces special fields with additional sql instructions in the query
+ *
+ * @param string $sqldata the sql query
+ * @param string $name affected field name
+ * @return string modified sql query
+ */
+ private function get_special_sql($sqldata, $name) {
+ if (substr($sqldata, 0, 12) === 'mydifficulty') {
+ return str_replace('mydifficulty', '(CASE WHEN sp.attempts > 0 THEN
+ ROUND(1 - (CAST(sp.correctattempts AS DECIMAL) / CAST(sp.attempts AS DECIMAL)), 2)
+ ELSE 0
+ END)', $sqldata);
+ }
+ if ($name == "onlynew") {
+ return str_replace('myattempts', 'sp.attempts', $sqldata);
+ }
+ return $sqldata;
+ }
+
+ /**
+ * Replaces fields with additional sql instructions in place of the field
+ *
+ * @param string $name affected field name
+ * @return string modified sql query
+ */
+ private function get_sql_field($name) {
+ if (substr($name, 0, 12) === 'mydifficulty') {
+ return str_replace('mydifficulty', '(CASE WHEN sp.attempts > 0 THEN
+ ROUND(1 - (CAST(sp.correctattempts AS DECIMAL) / CAST(sp.attempts AS DECIMAL)), 2)
+ ELSE 0
+ END)', $name);
+ }
+ if (substr($name, 0, 10) === 'myattempts') {
+ return 'sp.attempts';
+ }
+ return $this->get_sql_table_prefix($name) . $name;
+ }
+
+
+ /**
+ * Get the sql table prefix
+ *
+ * @param string $name
+ * @return string return sql prefix
+ */
+ private function get_sql_table_prefix($name) {
+ switch($name){
+ case 'difficultylevel':
+ return 'dl.';
+ case 'rate':
+ return 'vo.';
+ case 'publiccomment':
+ return 'copub.';
+ case 'state':
+ return 'sqq.';
+ case 'firstname':
+ case 'lastname':
+ return 'uc.';
+ case 'lastanswercorrect':
+ return 'sp.';
+ case 'mydifficulty':
+ return 'mydiffs.';
+ case 'myattempts':
+ return 'myatts.';
+ case 'myrate':
+ return 'myrate.';
+ case 'tagarray':
+ return 'tags.';
+ default;
+ return 'q.';
+ }
+ }
+
+ /**
+ * Provide SQL fragment to be ANDed into the WHERE clause to filter which questions are shown.
+ * @return string SQL fragment. Must use named parameters.
+ */
+ public function where() {
+ return implode(' AND ', $this->tests);
+ }
+
+ /**
+ * Return parameters to be bound to the above WHERE clause fragment.
+ * @return array parameter name => value.
+ */
+ public function params() {
+ return $this->params;
+ }
+}
diff --git a/classes/local/studentquiz_question.php b/classes/local/studentquiz_question.php
index 0efbc300..1a6962cc 100644
--- a/classes/local/studentquiz_question.php
+++ b/classes/local/studentquiz_question.php
@@ -40,10 +40,14 @@ class studentquiz_question {
private $cm;
/** @var \context_module $context - Context. */
- private $context;
+ protected $context;
/** @var stdClass - StudentQuiz data. */
- private $studentquiz;
+ protected $studentquiz;
+
+ /** @var int - StudentQuiz question id. */
+ protected $id;
+
/**
* studentquiz_question constructor.
diff --git a/classes/question/bank/attempts_column.php b/classes/question/bank/attempts_column.php
index ad0de71c..58c573c2 100644
--- a/classes/question/bank/attempts_column.php
+++ b/classes/question/bank/attempts_column.php
@@ -34,6 +34,15 @@ class attempts_column extends studentquiz_column_base {
/** @var \stdClass */
protected $studentquiz;
+ /** @var int category id*/
+ protected $categoryid;
+
+ /** @var int current user id*/
+ protected $currentuserid;
+
+ /** @var int student quiz id*/
+ protected $studentquizid;
+
/**
* Initialise Parameters for join
*/
diff --git a/classes/question/bank/difficulty_level_column.php b/classes/question/bank/difficulty_level_column.php
index 0c163a2a..25bfe4e6 100644
--- a/classes/question/bank/difficulty_level_column.php
+++ b/classes/question/bank/difficulty_level_column.php
@@ -34,6 +34,12 @@ class difficulty_level_column extends studentquiz_column_base {
/** @var \stdClass */
protected $studentquiz;
+ /** @var int current user id*/
+ protected $currentuserid;
+
+ /** @var int student quiz id*/
+ protected $studentquizid;
+
/**
* Initialise Parameters for join
*/
diff --git a/classes/question/bank/preview_column.php b/classes/question/bank/legacy/preview_column.php
similarity index 100%
rename from classes/question/bank/preview_column.php
rename to classes/question/bank/legacy/preview_column.php
diff --git a/classes/question/bank/sq_delete_action_column.php b/classes/question/bank/legacy/sq_delete_action_column.php
similarity index 100%
rename from classes/question/bank/sq_delete_action_column.php
rename to classes/question/bank/legacy/sq_delete_action_column.php
diff --git a/classes/question/bank/sq_edit_action_column.php b/classes/question/bank/legacy/sq_edit_action_column.php
similarity index 99%
rename from classes/question/bank/sq_edit_action_column.php
rename to classes/question/bank/legacy/sq_edit_action_column.php
index 63911379..3feaf7d5 100644
--- a/classes/question/bank/sq_edit_action_column.php
+++ b/classes/question/bank/legacy/sq_edit_action_column.php
@@ -15,7 +15,6 @@
// along with Moodle. If not, see .
namespace mod_studentquiz\bank;
-
use qbank_editquestion\edit_action_column;
use mod_studentquiz\local\studentquiz_helper;
diff --git a/classes/question/bank/legacy/sq_edit_menu_column_pre_43.php b/classes/question/bank/legacy/sq_edit_menu_column_pre_43.php
new file mode 100644
index 00000000..b1b6cf75
--- /dev/null
+++ b/classes/question/bank/legacy/sq_edit_menu_column_pre_43.php
@@ -0,0 +1,38 @@
+.
+
+namespace mod_studentquiz\bank;
+
+use core_question\local\bank\edit_menu_column;
+
+/**
+ * Represent edit column in studentquiz_bank_view which gathers together all the actions into a menu.
+ *
+ * @package mod_studentquiz
+ * @author Thong Bui
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_edit_menu_column_pre_43 extends edit_menu_column {
+ /**
+ * Title for this column.
+ *
+ * @return string Title of column
+ */
+ public function get_title() {
+ return get_string('actions');
+ }
+}
diff --git a/classes/question/bank/sq_hidden_action_column.php b/classes/question/bank/legacy/sq_hidden_action_column.php
similarity index 100%
rename from classes/question/bank/sq_hidden_action_column.php
rename to classes/question/bank/legacy/sq_hidden_action_column.php
diff --git a/classes/question/bank/sq_pin_action_column.php b/classes/question/bank/legacy/sq_pin_action_column.php
similarity index 100%
rename from classes/question/bank/sq_pin_action_column.php
rename to classes/question/bank/legacy/sq_pin_action_column.php
diff --git a/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php b/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php
new file mode 100644
index 00000000..cf22661e
--- /dev/null
+++ b/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php
@@ -0,0 +1,681 @@
+.
+
+/**
+ * The question bank view
+ *
+ *
+ * @package mod_studentquiz
+ * @copyright 2017 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_studentquiz\question\bank;
+
+use mod_studentquiz\local\studentquiz_helper;
+use mod_studentquiz\utils;
+use stdClass;
+use core_question\local\bank\question_version_status;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ .'/../../../../locallib.php');
+require_once(__DIR__ . '/../studentquiz_column_base.php');
+require_once(__DIR__ . '/../question_bank_filter.php');
+require_once(__DIR__ . '/../question_text_row.php');
+require_once(__DIR__ . '/../rate_column.php');
+require_once(__DIR__ . '/../difficulty_level_column.php');
+require_once(__DIR__ . '/../tag_column.php');
+require_once(__DIR__ . '/../attempts_column.php');
+require_once(__DIR__ . '/../comments_column.php');
+require_once(__DIR__ . '/../state_column.php');
+require_once(__DIR__ . '/../anonym_creator_name_column.php');
+require_once(__DIR__ . '/../state_pin_column.php');
+require_once(__DIR__ . '/../question_name_column.php');
+require_once(__DIR__ . '/preview_column.php');
+require_once(__DIR__ . '/sq_hidden_action_column.php');
+require_once(__DIR__ . '/sq_edit_action_column.php');
+require_once(__DIR__ . '/sq_pin_action_column.php');
+require_once(__DIR__ . '/sq_delete_action_column.php');
+require_once(__DIR__ . '/sq_edit_menu_column_pre_43.php');
+
+/**
+ * Module instance settings form
+ *
+ * @package mod_studentquiz
+ * @copyright 2017 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class studentquiz_bank_view_pre_43 extends \core_question\local\bank\view {
+ /**
+ * @var stdClass filtered questions from database
+ */
+ private $questions;
+
+ /**
+ * @var array of ids of the questions that are displayed on current page
+ * (IF the filter result is paginated, ids on other pages are not collected!)
+ */
+ private $displayedquestionsids = array();
+
+ /**
+ * @var int totalnumber from filtered questions
+ */
+ private $totalnumber;
+
+ /**
+ * @var string sql tag name field
+ */
+ private $tagnamefield;
+
+ /**
+ * @var bool is filter active
+ */
+ private $isfilteractive;
+
+ /**
+ * @var question_bank_filter_form class
+ */
+ private $filterform;
+
+ /**
+ * @var array of user_filter_*
+ */
+ private $fields;
+
+ /**
+ * @var object $studentquiz current studentquiz record
+ */
+ private $studentquiz;
+
+ /**
+ * @var \core\dml\sql_join Current group join sql.
+ */
+ private $currentgroupjoinsql;
+
+ /**
+ * @var int Currently viewing user id.
+ */
+ protected $userid;
+
+
+ /**
+ * @var mixed
+ */
+ private $pagevars;
+
+ /**
+ * @var stdClass StudentQuiz renderer.
+ */
+ protected $renderer;
+
+ /** @var mod_studentquiz_report */
+ protected $report;
+
+ /**
+ * Constructor assuming we already have the necessary data loaded.
+ *
+ * @param \core_question\local\bank\question_edit_contexts $contexts
+ * @param \moodle_url $pageurl
+ * @param object $course
+ * @param object|null $cm
+ * @param object $studentquiz
+ * @param mixed $pagevars
+ * @param mod_studentquiz_report $report
+ */
+ public function __construct($contexts, $pageurl, $course, $cm, $studentquiz, $pagevars, $report) {
+ $this->set_filter_post_data();
+ global $USER, $PAGE;
+ $this->pagevars = $pagevars;
+ $this->studentquiz = $studentquiz;
+ $this->userid = $USER->id;
+ $this->report = $report;
+ parent::__construct($contexts, $pageurl, $course, $cm);
+ $this->set_filter_form_fields($this->is_anonymized());
+ $this->initialize_filter_form($pageurl);
+ $currentgroup = groups_get_activity_group($cm, true);
+ $this->currentgroupjoinsql = utils::groups_get_questions_joins($currentgroup, 'sqq.groupid');
+ // Init search conditions with filterform state.
+ $categorycondition = new \core_question\bank\search\category_condition(
+ $pagevars['cat'], $pagevars['recurse'], $contexts, $pageurl, $course);
+ $studentquizcondition = new \mod_studentquiz\condition\studentquiz_condition_pre_43($cm, $this->filterform,
+ $this->report, $studentquiz);
+ $this->isfilteractive = $studentquizcondition->is_filter_active();
+ $this->searchconditions = array ($categorycondition, $studentquizcondition);
+ $this->renderer = $PAGE->get_renderer('mod_studentquiz', 'overview');
+ }
+
+ /**
+ * Shows the question bank interface.
+ *
+ * The function also processes a number of actions:
+ *
+ * Actions affecting the question pool:
+ * move Moves a question to a different category
+ * deleteselected Deletes the selected questions from the category
+ * Other actions:
+ * category Chooses the category
+ * params: $tabname question bank edit tab name, for permission checking
+ * $pagevars current list of page variables
+ *
+ * @param array $pagevars
+ * @param string $tabname
+ */
+ public function display($pagevars, $tabname): void {
+ $page = $pagevars['qpage'];
+ $perpage = $pagevars['qperpage'];
+ $cat = $pagevars['cat'];
+ $recurse = $pagevars['recurse'];
+ $showhidden = $pagevars['showhidden'];
+ $showquestiontext = $pagevars['qbshowtext'];
+ $tagids = [];
+ if (!empty($pagevars['qtagids'])) {
+ $tagids = $pagevars['qtagids'];
+ }
+ $output = '';
+
+ $this->build_query();
+
+ // Get result set.
+ $questions = $this->load_questions($page, $perpage);
+ $this->questions = $questions;
+ $this->countsql = count($this->questions);
+ if ($this->countsql || $this->isfilteractive) {
+ // We're unable to force the filter form to submit with get method. We have 2 forms on the page
+ // which need to interact with each other, so forcing method as get here.
+ $output .= str_replace('method="post"', 'method="get"', $this->renderer->render_filter_form($this->filterform));
+ }
+ echo $output;
+ if ($this->countsql > 0) {
+ $this->display_question_list($this->baseurl, $cat, null, $page, $perpage,
+ $this->contexts->having_cap('moodle/question:add')
+ );
+ } else {
+ list($message, $questionsubmissionallow) = mod_studentquiz_check_availability($this->studentquiz->opensubmissionfrom,
+ $this->studentquiz->closesubmissionfrom, 'submission');
+ if ($questionsubmissionallow) {
+ echo $this->renderer->render_no_questions_notification($this->isfilteractive);
+ }
+ }
+ }
+
+ /**
+ * Get all questions
+ * @return stdClass array of questions
+ */
+ public function get_questions() {
+ return $this->questions;
+ }
+
+ /**
+ * Override base default sort
+ */
+ protected function default_sort(): array {
+ return [
+ 'mod_studentquiz\bank\anonym_creator_name_column-timecreated' => -1,
+ 'mod_studentquiz\bank\question_name_column' => 1,
+ ];
+ }
+
+ /**
+ * Create the SQL query to retrieve the indicated questions, based on
+ * \core_question\local\bank\search\condition filters.
+ */
+ protected function build_query(): void {
+ global $CFG;
+
+ // Hard coded setup.
+ $params = array();
+ $joins = [
+ 'qv' => 'JOIN {question_versions} qv ON qv.questionid = q.id',
+ 'qbe' => 'JOIN {question_bank_entries} qbe on qbe.id = qv.questionbankentryid',
+ 'qc' => 'JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid',
+ 'qr' => "JOIN {question_references} qr ON qr.questionbankentryid = qbe.id AND 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)
+ AND qr.component = 'mod_studentquiz'
+ AND qr.questionarea = 'studentquiz_question'
+ AND qc.contextid = qr.usingcontextid",
+ 'sqq' => 'JOIN {studentquiz_question} sqq ON sqq.id = qr.itemid'
+ ];
+ $fields = [
+ 'sqq.id studentquizquestionid',
+ 'qc.id categoryid',
+ 'qv.version',
+ 'qv.id versionid',
+ 'qbe.id questionbankentryid',
+ 'qv.status',
+ 'q.timecreated',
+ 'q.createdby',
+ ];
+ // Only show ready and draft question.
+ $tests = [
+ 'q.parent = 0',
+ "qv.status <> :status",
+ ];
+ $params['status'] = question_version_status::QUESTION_STATUS_HIDDEN;
+ 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);
+ if ($this->currentgroupjoinsql->wheres) {
+ $params += $this->currentgroupjoinsql->params;
+ $tests[] = $this->currentgroupjoinsql->wheres;
+ }
+
+ // Build the order by clause.
+ $sorts = array();
+ 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 and load params from search conditions.
+ foreach ($this->searchconditions as $searchcondition) {
+ if (!empty($searchcondition->where())) {
+ $tests[] = $searchcondition->where();
+ }
+ if (!empty($searchcondition->params())) {
+ $params = array_merge($params, $searchcondition->params());
+ }
+ }
+ array_unshift($sorts, 'sqq.pinned DESC');
+
+ // Build the complete SQL query.
+ $sql = ' FROM {question} q ' . implode(' ', $joins);
+ $sql .= ' WHERE ' . implode(' AND ', $tests);
+ $this->sqlparams = $params;
+ $this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts);
+ }
+
+ /**
+ * Has questions in category
+ * @return bool
+ */
+ protected function has_questions_in_category() {
+ return $this->totalnumber > 0;
+ }
+
+ /**
+ * Create new default question form
+ * @param int $categoryid question category
+ * @param bool $canadd capability state
+ */
+ public function create_new_question_form($categoryid, $canadd): void {
+ global $OUTPUT;
+
+ $output = '';
+
+ $caption = get_string('createnewquestion', 'studentquiz');
+
+ if ($canadd) {
+ $returnurl = $this->baseurl;
+ $params = array(
+ // TODO: MAGIC CONSTANT!
+ 'returnurl' => $returnurl->out_as_local_url(false),
+ 'category' => $categoryid,
+ 'cmid' => $this->studentquiz->coursemodule,
+ );
+
+ $url = new \moodle_url('/question/bank/editquestion/addquestion.php', $params);
+
+ $allowedtypes = (empty($this->studentquiz->allowedqtypes)) ? 'ALL' : $this->studentquiz->allowedqtypes;
+ $allowedtypes = ($allowedtypes == 'ALL') ? mod_studentquiz_get_question_types_keys() : explode(',', $allowedtypes);
+ $qtypecontainer = \html_writer::div(
+ \qbank_editquestion\editquestion_helper::print_choose_qtype_to_add_form(array(), $allowedtypes, true
+ ), '', array('id' => 'qtypechoicecontainer'));
+ $questionsubmissionbutton = new \single_button($url, $caption, 'get', 'primary');
+
+ list($message, $questionsubmissionallow) = mod_studentquiz_check_availability($this->studentquiz->opensubmissionfrom,
+ $this->studentquiz->closesubmissionfrom, 'submission');
+
+ $questionsubmissionbutton->disabled = !$questionsubmissionallow;
+ $output .= \html_writer::div($OUTPUT->render($questionsubmissionbutton) . $qtypecontainer, 'createnewquestion py-3');
+
+ if (!empty($message)) {
+ $output .= $this->renderer->render_availability_message($message, 'mod_studentquiz_submission_info');
+ }
+ } else {
+ $output .= $this->renderer->render_warning_message(get_string('nopermissionadd', 'question'));
+ }
+ echo $output;
+ }
+
+ /**
+ * Prints the table of questions in a category with interactions
+ *
+ * @param \moodle_url $pageurl The URL to reload this page.
+ * @param string $categoryandcontext 'categoryID,contextID'.
+ * @param int $recurse Whether to include subcategories.
+ * @param int $page The number of the page to be displayed
+ * @param int $perpage Number of questions to show per page
+ * @param array $addcontexts contexts where the user is allowed to add new questions.
+ */
+ protected function display_question_list($pageurl, $categoryandcontext, $recurse = 1, $page = 0,
+ $perpage = 100, $addcontexts = []): void {
+ $output = '';
+ $category = $this->get_current_category($categoryandcontext);
+ list($categoryid, $contextid) = explode(',', $categoryandcontext);
+ $catcontext = \context::instance_by_id($contextid);
+
+ $output .= \html_writer::start_tag('fieldset', array('class' => 'invisiblefieldset', 'style' => 'display:block;'));
+
+ $output .= $this->renderer->render_hidden_field($this->cm->id, $this->baseurl, $perpage);
+
+ $output .= $this->renderer->render_control_buttons($catcontext, $this->has_questions_in_category(),
+ $addcontexts, $category);
+
+ $output .= $this->renderer->render_pagination_bar($this->pagevars, $this->baseurl, $this->totalnumber, $page,
+ $perpage, true);
+
+ $output .= $this->display_question_list_rows();
+
+ $output .= $this->renderer->render_pagination_bar($this->pagevars, $this->baseurl, $this->totalnumber, $page,
+ $perpage, false);
+
+ $output .= $this->renderer->render_control_buttons($catcontext, $this->has_questions_in_category(),
+ $addcontexts, $category);
+
+ $output .= \html_writer::end_tag('fieldset');
+ $output = $this->renderer->render_question_form($output);
+ $output .= $this->renderer->display_javascript_snippet();
+
+ echo $output;
+ }
+
+ /**
+ * Prints the effective question table
+ *
+ * @return string
+ */
+ protected function display_question_list_rows() {
+ $output = '';
+ $output .= \html_writer::start_div('categoryquestionscontainer');
+ ob_start();
+ $this->print_table($this->questions);
+ $output .= ob_get_contents();
+ ob_end_clean();
+ $output .= \html_writer::end_div();
+ return $output;
+ }
+
+ /**
+ * Return the row classes for question table
+ *
+ * @param object $question the row from the $question table, augmented with extra information.
+ * @param int $rowcount Row index
+ * @return array Classes of row
+ */
+ protected function get_row_classes($question, $rowcount): array {
+ $classes = parent::get_row_classes($question, $rowcount);
+ if (($key = array_search('dimmed_text', $classes)) !== false) {
+ unset($classes[$key]);
+ }
+ return $classes;
+ }
+
+ /**
+ * Set filter form fields
+ * @param bool $anonymize if false, questions can get filtered by author last name and first name instead by own userid only.
+ */
+ private function set_filter_form_fields($anonymize = true) {
+ $this->fields = array();
+
+ // Fast filters.
+ $this->fields[] = new \toggle_filter_checkbox('onlynew',
+ get_string('filter_label_onlynew', 'studentquiz'),
+ false, 'myattempts', array('myattempts', 'myattempts_op'), 0, 0,
+ get_string('filter_label_onlynew_help', 'studentquiz'));
+
+ $this->fields[] = new \toggle_filter_checkbox('only_new_state',
+ get_string('state_newplural', 'studentquiz'), false, 'sqq.state',
+ ['approved'], 2, studentquiz_helper::STATE_NEW);
+ $this->fields[] = new \toggle_filter_checkbox('only_approved_state',
+ get_string('state_approvedplural', 'studentquiz'), false, 'sqq.state',
+ ['approved'], 2, studentquiz_helper::STATE_APPROVED);
+ $this->fields[] = new \toggle_filter_checkbox('only_disapproved_state',
+ get_string('state_disapprovedplural', 'studentquiz'), false, 'sqq.state',
+ ['approved'], 2, studentquiz_helper::STATE_DISAPPROVED);
+ $this->fields[] = new \toggle_filter_checkbox('only_changed_state',
+ get_string('state_changedplural', 'studentquiz'), false, 'sqq.state',
+ ['approved'], 2, studentquiz_helper::STATE_CHANGED);
+ $this->fields[] = new \toggle_filter_checkbox('only_reviewable_state',
+ get_string('state_reviewableplural', 'studentquiz'), false, 'sqq.state',
+ ['approved'], 2, studentquiz_helper::STATE_REVIEWABLE);
+
+ $this->fields[] = new \toggle_filter_checkbox('onlygood',
+ get_string('filter_label_onlygood', 'studentquiz'),
+ false, 'vo.rate', array('rate', 'rate_op'), 1, 4,
+ get_string('filter_label_onlygood_help', 'studentquiz', '4'));
+
+ $this->fields[] = new \toggle_filter_checkbox('onlymine',
+ get_string('filter_label_onlymine', 'studentquiz'),
+ false, 'q.createdby', array('createdby'), 2, $this->userid,
+ get_string('filter_label_onlymine_help', 'studentquiz'));
+
+ $this->fields[] = new \toggle_filter_checkbox('onlydifficultforme',
+ get_string('filter_label_onlydifficultforme', 'studentquiz'),
+ false, 'mydifficulty', array('mydifficulty', 'mydifficulty_op'), 1, 0.60,
+ get_string('filter_label_onlydifficultforme_help', 'studentquiz', '60'));
+
+ $this->fields[] = new \toggle_filter_checkbox('onlydifficult',
+ get_string('filter_label_onlydifficult', 'studentquiz'),
+ false, 'dl.difficultylevel', array('difficultylevel', 'difficultylevel_op'), 1, 0.60,
+ get_string('filter_label_onlydifficult_help', 'studentquiz', '60'));
+
+ // Advanced filters.
+ $this->fields[] = new \studentquiz_user_filter_text('tagarray', get_string('filter_label_tags', 'studentquiz'),
+ true, 'tagarray');
+
+ $states = array();
+ foreach (studentquiz_helper::$statename as $num => $name) {
+ if ($num == studentquiz_helper::STATE_DELETE || $num == studentquiz_helper::STATE_HIDE) {
+ continue;
+ }
+ $states[$num] = get_string('state_'.$name, 'studentquiz');
+ }
+ $this->fields[] = new \user_filter_simpleselect('state', get_string('state_column_name', 'studentquiz'),
+ true, 'state', $states);
+
+ $this->fields[] = new \user_filter_number('rate', get_string('filter_label_rates', 'studentquiz'),
+ true, 'rate');
+ $this->fields[] = new \user_filter_percent('difficultylevel', get_string('filter_label_difficulty_level', 'studentquiz'),
+ true, 'difficultylevel');
+
+ $this->fields[] = new \user_filter_number('publiccomment', get_string('filter_label_comment', 'studentquiz'),
+ true, 'publiccomment');
+ $this->fields[] = new \studentquiz_user_filter_text('name', get_string('filter_label_question', 'studentquiz'),
+ true, 'name');
+ $this->fields[] = new \studentquiz_user_filter_text('questiontext', get_string('filter_label_questiontext', 'studentquiz'),
+ true, 'questiontext');
+
+ if ($anonymize) {
+ $this->fields[] = new \user_filter_checkbox('createdby', get_string('filter_label_show_mine', 'studentquiz'),
+ true, 'createdby');
+ } else {
+ $this->fields[] = new \studentquiz_user_filter_text('firstname', get_string('firstname'), true, 'firstname');
+ $this->fields[] = new \studentquiz_user_filter_text('lastname', get_string('lastname'), true, 'lastname');
+ }
+
+ $this->fields[] = new \studentquiz_user_filter_date('timecreated', get_string('filter_label_createdate', 'studentquiz'),
+ true, 'timecreated');
+
+ $this->fields[] = new \user_filter_simpleselect('lastanswercorrect',
+ get_string('filter_label_mylastattempt', 'studentquiz'),
+ true, 'lastanswercorrect', array(
+ '1' => get_string('lastattempt_right', 'studentquiz'),
+ '0' => get_string('lastattempt_wrong', 'studentquiz')
+ ));
+
+ $this->fields[] = new \user_filter_number('myattempts', get_string('filter_label_myattempts', 'studentquiz'),
+ true, 'myattempts');
+
+ $this->fields[] = new \user_filter_number('mydifficulty', get_string('filter_label_mydifficulty', 'studentquiz'),
+ true, 'mydifficulty');
+
+ $this->fields[] = new \user_filter_number('myrate', get_string('filter_label_myrate', 'studentquiz'),
+ true, 'myrate');
+ }
+
+ /**
+ * Set data for filter recognition
+ * We have two forms in the view.php page which need to interact with each other. All params are sent through GET,
+ * but the moodle filter form can only process POST, so we need to copy them there.
+ */
+ private function set_filter_post_data() {
+ $_POST = $_GET;
+ }
+
+ /**
+ * Initialize filter form
+ * @param moodle_url $pageurl
+ */
+ private function initialize_filter_form($pageurl) {
+ $this->isfilteractive = false;
+
+ // If reset button was pressed, redirect the user again to the page.
+ // This means all submitted data is intentionally lost and thus the form clean again.
+ if (optional_param('resetbutton', false, PARAM_ALPHA)) {
+ // Reset to clean state.
+ $pageurl->remove_all_params();
+ $pageurl->params(['id' => $this->cm->id]);
+ redirect($pageurl->out());
+ }
+ $this->filterform = new \mod_studentquiz_question_bank_filter_form(
+ $this->fields,
+ $pageurl->out(false),
+ array_merge(['cmid' => $this->cm->id], $this->pagevars)
+ );
+ }
+
+ /**
+ * Load question from database
+ * @param int $page
+ * @param int $perpage
+ * @return paginated array of questions
+ */
+ private function load_questions($page, $perpage) {
+ global $DB;
+ $rs = $DB->get_recordset_sql($this->loadsql, $this->sqlparams);
+
+ $counterquestions = 0;
+ $numberofdisplayedquestions = 0;
+ $showall = $this->pagevars['showall'];
+ $rs->rewind();
+
+ // Skip Questions on previous pages.
+ while ($rs->valid() && !$showall && $counterquestions < $page * $perpage) {
+ $rs->next();
+ $counterquestions++;
+ }
+
+ // Reset and start from 0 if page was empty.
+ if (!$showall && $counterquestions < $page * $perpage) {
+ $rs->rewind();
+ }
+
+ // Unfortunately we cant just render the questions directly.
+ // We need to annotate tags first.
+ $questions = array();
+ // Load questions.
+ while ($rs->valid() && ($showall || $numberofdisplayedquestions < $perpage)) {
+ $question = $rs->current();
+ $numberofdisplayedquestions++;
+ $counterquestions++;
+ $this->displayedquestionsids[] = $question->id;
+ $rs->next();
+ $questions[] = $question;
+ }
+
+ // Iterate to end.
+ while ($rs->valid()) {
+ $rs->next();
+ $counterquestions++;
+ }
+ $this->totalnumber = $counterquestions;
+ $rs->close();
+ return $questions;
+ }
+
+ /**
+ * TODO: rename function and apply (there is duplicate method)
+ * @return bool studentquiz is set to anoymize ranking.
+ */
+ public function is_anonymized() {
+ if (!$this->studentquiz->anonymrank) {
+ return false;
+ }
+ $context = \context_module::instance($this->studentquiz->coursemodule);
+ if (has_capability('mod/studentquiz:unhideanonymous', $context)) {
+ return false;
+ }
+ // Instance is anonymized and isn't allowed to unhide that.
+ return true;
+ }
+
+ /**
+ * Get Studentquiz object of question bank.
+ * @return \stdClass studentquiz object.
+ */
+ public function get_studentquiz() {
+ return $this->studentquiz;
+ }
+
+ /**
+ * Deal with a sort name of the form columnname, or colname_subsort by
+ * breaking it up, validating the bits that are present, and returning them.
+ * If there is no subsort, then $subsort is returned as ''.
+ *
+ * @param string $sort the sort parameter to process.
+ * @return array array($colname, $subsort).
+ */
+ protected function parse_subsort($sort): array {
+ // When we sort by public/private comments and turn off the setting studentquiz | privatecomment,
+ // the parse_subsort function will throw exception. We should redirect to the base_url after cleaning all sort params.
+ $showprivatecomment = $this->studentquiz->privatecommenting;
+ if ($showprivatecomment && $sort == 'mod_studentquiz\bank\comment_column' ||
+ !$showprivatecomment && ($sort == 'mod_studentquiz\bank\comment_column-privatecomment' ||
+ $sort == 'mod_studentquiz\bank\comment_column-publiccomment')) {
+ for ($i = 1; $i <= self::MAX_SORTS; $i++) {
+ $this->baseurl->remove_params('qbs' . $i);
+ }
+ redirect($this->base_url());
+ }
+
+ return parent::parse_subsort($sort);
+ }
+
+ /**
+ * Return the all the required column for the view.
+ *
+ * @return \question_bank_column_base[]
+ */
+ protected function wanted_columns(): array {
+ global $PAGE;
+ $renderer = $PAGE->get_renderer('mod_studentquiz');
+ $this->requiredcolumns = $renderer->get_columns_for_question_bank_view_pre_43($this);
+ return $this->requiredcolumns;
+ }
+}
diff --git a/classes/question/bank/rate_column.php b/classes/question/bank/rate_column.php
index cad474e4..a1df9e0d 100644
--- a/classes/question/bank/rate_column.php
+++ b/classes/question/bank/rate_column.php
@@ -31,6 +31,12 @@ class rate_column extends studentquiz_column_base {
*/
protected $renderer;
+ /** @var int current user id*/
+ protected $currentuserid;
+
+ /** @var int student quiz id*/
+ protected $studentquizid;
+
/**
* Initialise Parameters for join
*/
diff --git a/classes/question/bank/sq_delete_action.php b/classes/question/bank/sq_delete_action.php
new file mode 100644
index 00000000..5c811284
--- /dev/null
+++ b/classes/question/bank/sq_delete_action.php
@@ -0,0 +1,51 @@
+.
+
+namespace mod_studentquiz\bank;
+
+use qbank_deletequestion\delete_action;
+use mod_studentquiz\local\studentquiz_helper;
+
+/**
+ * Represent delete action in studentquiz_bank_view.
+ *
+ * @package mod_studentquiz
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_delete_action extends delete_action {
+
+ /**
+ * Override method to get url and label for delete action of the studentquiz.
+ *
+ * @param \stdClass $question The row from the $question table, augmented with extra information.
+ * @return array With three elements.
+ * $url - The URL to perform the action.
+ * $icon - The icon for this action.
+ * $label - Text label to display in the UI (either in the menu, or as a tool-tip on the icon).
+ */
+ protected function get_url_icon_and_label(\stdClass $question): array {
+
+ if ($question->state == studentquiz_helper::STATE_APPROVED &&
+ !has_capability('mod/studentquiz:previewothers', $this->qbank->get_most_specific_context())) {
+ // Do not render delete icon if the question is in approved state for the student.
+ return [null, null, null];
+ }
+
+ return parent::get_url_icon_and_label($question);
+ }
+
+}
diff --git a/classes/question/bank/sq_edit_action.php b/classes/question/bank/sq_edit_action.php
new file mode 100644
index 00000000..7cf12241
--- /dev/null
+++ b/classes/question/bank/sq_edit_action.php
@@ -0,0 +1,52 @@
+.
+
+namespace mod_studentquiz\bank;
+
+use qbank_editquestion\edit_action;
+use mod_studentquiz\local\studentquiz_helper;
+
+/**
+ * Represent edit action in studentquiz_bank_view
+ *
+ * @package mod_studentquiz
+ * @author Huong Nguyen
+ * @copyright 2019 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_edit_action extends edit_action {
+
+ /**
+ * Override method to get url and label for edit action of the studentquiz.
+ *
+ * @param \stdClass $question The row from the $question table, augmented with extra information.
+ * @return array With three elements.
+ * $url - The URL to perform the action.
+ * $icon - The icon for this action.
+ * $label - Text label to display in the UI (either in the menu, or as a tool-tip on the icon)
+ */
+ protected function get_url_icon_and_label(\stdClass $question): array {
+
+ if (($question->state == studentquiz_helper::STATE_APPROVED || $question->state == studentquiz_helper::STATE_DISAPPROVED) &&
+ !has_capability('mod/studentquiz:previewothers', $this->qbank->get_most_specific_context())) {
+ // Do not render Edit icon if Question is in approved/disapproved state for Student.
+ return [null, null, null];
+ }
+
+ return parent::get_url_icon_and_label($question);
+ }
+
+}
diff --git a/classes/question/bank/sq_edit_menu_column.php b/classes/question/bank/sq_edit_menu_column.php
index 19553967..5e9b8237 100644
--- a/classes/question/bank/sq_edit_menu_column.php
+++ b/classes/question/bank/sq_edit_menu_column.php
@@ -27,12 +27,75 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sq_edit_menu_column extends edit_menu_column {
+
+ /** @var int */
+ protected $currentuserid;
+
+ /**
+ * Output the contents of this column.
+ *
+ * @param object $question the row from the $question table, augmented with extra information.
+ * @param string $rowclasses CSS class names that should be applied to this row of output.
+ */
+ protected function display_content($question, $rowclasses): void {
+ global $OUTPUT;
+ $actions = $this->qbank->get_question_actions();
+
+ $menu = new \action_menu();
+ $menu->set_menu_trigger(get_string('edit'));
+ foreach ($actions as $action) {
+ $action = $action->get_action_menu_link($question);
+ if ($action) {
+ $menu->add($action);
+ }
+ }
+
+ echo $OUTPUT->render($menu);
+ }
+
+ /**
+ * Initialise Parameters for join
+ */
+ protected function init(): void {
+ global $USER;
+ $this->currentuserid = $USER->id;
+ parent::init();
+ }
+
+ /**
+ * Return an array 'table_alias' => 'JOIN clause' to bring in any data that
+ * this column required.
+ *
+ * The return values for all the columns will be checked. It is OK if two
+ * columns join in the same table with the same alias and identical JOIN clauses.
+ * If to columns try to use the same alias with different joins, you get an error.
+ * The only table included by default is the question table, which is aliased to 'q'.
+ *
+ * It is important that your join simply adds additional data (or NULLs) to the
+ * existing rows of the query. It must not cause additional rows.
+ *
+ * @return array 'table_alias' => 'JOIN clause'
+ */
+ public function get_extra_joins(): array {
+ $hidden = "sqq.hidden = 0";
+ $mine = "q.createdby = $this->currentuserid";
+
+ // Without permission, a user can only see non-hidden question or its their own.
+ $sqlextra = "AND ($hidden OR $mine)";
+ if (has_capability('mod/studentquiz:previewothers', $this->qbank->get_most_specific_context())) {
+ $sqlextra = "";
+ }
+
+ return ['sqh' => "JOIN {studentquiz_question} sqh ON sqh.id = qr.itemid $sqlextra"];
+ }
+
/**
- * Title for this column.
+ * Required columns
*
- * @return string Title of column
+ * @return array fields required. use table alias 'q' for the question table, or one of the
+ * ones from get_extra_joins. Every field requested must specify a table prefix.
*/
- public function get_title() {
- return get_string('actions');
+ public function get_required_fields(): array {
+ return ['sqq.hidden AS sq_hidden'];
}
}
diff --git a/classes/question/bank/sq_hidden_action.php b/classes/question/bank/sq_hidden_action.php
new file mode 100644
index 00000000..1409446a
--- /dev/null
+++ b/classes/question/bank/sq_hidden_action.php
@@ -0,0 +1,91 @@
+.
+
+namespace mod_studentquiz\bank;
+
+use core_question\local\bank\question_action_base;
+
+/**
+ * Represent sq_hiden action in studentquiz_bank_view
+ *
+ * @package mod_studentquiz
+ * @author Huong Nguyen
+ * @copyright 2019 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_hidden_action extends question_action_base {
+
+ /** @var int */
+ protected $currentuserid;
+
+ /**
+ * Initialise Parameters for join
+ */
+ protected function init(): void {
+ global $USER;
+ $this->currentuserid = $USER->id;
+ parent::init();
+ }
+
+ /**
+ * Column name
+ *
+ * @return string internal name for this column. Used as a CSS class name,
+ * and to store information about the current sort. Must match PARAM_ALPHA.
+ */
+ public function get_name() {
+ return 'sq_hidden';
+ }
+
+ /**
+ * Override method to get url and label for show/hidden action of the studentquiz.
+ *
+ * @param \stdClass $question The row from the $question table, augmented with extra information.
+ * @return array With three elements.
+ * $url - The URL to perform the action.
+ * $icon - The icon for this action.
+ * $label - Text label to display in the UI (either in the menu, or as a tool-tip on the icon)
+ */
+ protected function get_url_icon_and_label(\stdClass $question): array {
+ $courseid = $this->qbank->get_courseid();
+ $cmid = $this->qbank->cm->id;
+ if (has_capability('mod/studentquiz:previewothers', $this->qbank->get_most_specific_context())) {
+ if (!empty($question->sq_hidden)) {
+ $url = new \moodle_url('/mod/studentquiz/hideaction.php',
+ ['studentquizquestionid' => $question->studentquizquestionid, 'sesskey' => sesskey(),
+ 'courseid' => $courseid, 'hide' => 0, 'cmid' => $cmid, 'returnurl' => $this->qbank->base_url()]);
+ return [$url, 't/show', get_string('show')];
+ } else {
+ $url = new \moodle_url('/mod/studentquiz/hideaction.php',
+ ['studentquizquestionid' => $question->studentquizquestionid, 'sesskey' => sesskey(),
+ 'courseid' => $courseid, 'hide' => 1, 'cmid' => $cmid, 'returnurl' => $this->qbank->base_url()]);
+ return [$url, 't/hide', get_string('hide')];
+ }
+ }
+
+ return [null, null, null];
+ }
+
+ /**
+ * Required columns
+ *
+ * @return array fields required. use table alias 'q' for the question table, or one of the
+ * ones from get_extra_joins. Every field requested must specify a table prefix.
+ */
+ public function get_required_fields(): array {
+ return ['sqq.hidden AS sq_hidden'];
+ }
+}
diff --git a/classes/question/bank/sq_pin_action.php b/classes/question/bank/sq_pin_action.php
new file mode 100644
index 00000000..6c4d8fc7
--- /dev/null
+++ b/classes/question/bank/sq_pin_action.php
@@ -0,0 +1,96 @@
+.
+
+
+namespace mod_studentquiz\bank;
+
+use core_question\local\bank\question_action_base;
+use moodle_url;
+
+/**
+ * Represent pin action in studentquiz_bank_view
+ *
+ * @package mod_studentquiz
+ * @copyright 2021 The Open University.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_pin_action extends question_action_base {
+ /** @var mod_studentquiz Renderer of student quiz. */
+ protected $renderer;
+
+ protected $currentuserid;
+
+ /**
+ * Init method.
+ */
+ protected function init(): void {
+ global $USER, $PAGE;
+ $this->currentuserid = $USER->id;
+ $this->renderer = $PAGE->get_renderer('mod_studentquiz');
+ }
+
+ /**
+ * Get the internal name for this column.
+ *
+ * @return string Column name.
+ */
+ public function get_name() {
+ return 'pin_toggle';
+ }
+
+ /**
+ * Get required fields.
+ *
+ * @return array Fields required.
+ */
+ public function get_required_fields(): array {
+ return array('sqq.pinned AS pinned');
+ }
+
+
+ /**
+ * Override method to get url and label for pin action of the studentquiz.
+ *
+ * @param \stdClass $question The row from the $question table, augmented with extra information.
+ * @return array With three elements.
+ * $url - The URL to perform the action.
+ * $icon - The icon for this action.
+ * $label - Text label to display in the UI (either in the menu, or as a tool-tip on the icon)
+ */
+ protected function get_url_icon_and_label(\stdClass $question): array {
+ $output = '';
+ $courseid = $this->qbank->get_courseid();
+ $cmid = $this->qbank->cm->id;
+ if (has_capability('mod/studentquiz:pinquestion', $this->qbank->get_most_specific_context())) {
+ if ($question->pinned) {
+ $url = new moodle_url('/mod/studentquiz/pinaction.php',
+ ['studentquizquestionid' => $question->studentquizquestionid,
+ 'pin' => 0, 'sesskey' => sesskey(), 'cmid' => $cmid,
+ 'returnurl' => $this->qbank->base_url(), 'courseid' => $courseid]);
+ return [$url, 'i/star', get_string('unpin', 'studentquiz'), 'courseid' => $courseid];
+ } else {
+ $url = new moodle_url('/mod/studentquiz/pinaction.php',
+ ['studentquizquestionid' => $question->studentquizquestionid,
+ 'pin' => 1, 'sesskey' => sesskey(), 'cmid' => $cmid,
+ 'returnurl' => $this->qbank->base_url(), 'courseid' => $courseid]);
+ return [$url, 't/emptystar', get_string('pin', 'studentquiz')];
+ }
+ }
+
+ return [null, null, null];
+ }
+
+}
diff --git a/classes/question/bank/sq_preview_action.php b/classes/question/bank/sq_preview_action.php
new file mode 100644
index 00000000..99f09619
--- /dev/null
+++ b/classes/question/bank/sq_preview_action.php
@@ -0,0 +1,77 @@
+.
+
+namespace mod_studentquiz\bank;
+
+/**
+ * A action type for preview link to mod_studentquiz_preview
+ *
+ * @package mod_studentquiz
+ * @copyright 2017 HSR (http://www.hsr.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sq_preview_action extends \qbank_previewquestion\preview_action {
+
+ /**
+ * Renderer
+ * @var stdClass
+ */
+ protected $renderer;
+
+ /** @var stdClass */
+ protected $context;
+
+ /** @var string */
+ protected $previewtext;
+
+ /**
+ * Loads config of current userid and can see
+ */
+ public function init(): void {
+ global $PAGE;
+ $this->renderer = $PAGE->get_renderer('mod_studentquiz');
+ $this->context = $this->qbank->get_most_specific_context();
+ $this->previewtext = get_string('preview');
+ }
+
+ /**
+ * Look up if current user is allowed to preview this question
+ * @param object $question The current question object
+ * @return boolean
+ */
+ private function can_preview($question) {
+ global $USER;
+ return ($question->createdby == $USER->id) || has_capability('mod/studentquiz:previewothers', $this->context);
+ }
+
+ /**
+ * Override this function and return the appropriate action menu link, or null if it does not apply to this question.
+ *
+ * @param \stdClass $question Data about the question being displayed in this row.
+ * @return \action_menu_link|null The action, if applicable to this question.
+ */
+ public function get_action_menu_link(\stdClass $question): ?\action_menu_link {
+ if ($this->can_preview($question)) {
+ $params = ['cmid' => $this->context->instanceid, 'studentquizquestionid' => $question->studentquizquestionid];
+ $link = new \moodle_url('/mod/studentquiz/preview.php', $params);
+
+ return new \action_menu_link_secondary($link, new \pix_icon('t/preview', ''),
+ $this->previewtext, ['target' => 'questionpreview']);
+ }
+
+ return null;
+ }
+}
diff --git a/classes/question/bank/state_pin_column.php b/classes/question/bank/state_pin_column.php
index ca29f045..de3e382d 100644
--- a/classes/question/bank/state_pin_column.php
+++ b/classes/question/bank/state_pin_column.php
@@ -26,10 +26,13 @@
* @copyright 2021 The Open University.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class state_pin_column extends action_column_base {
+class state_pin_column extends \core_question\local\bank\column_base {
/** @var mod_studentquiz Renderer of student quiz. */
protected $renderer;
+ /** @var int current user id*/
+ protected $currentuserid;
+
/**
* Init method.
*/
@@ -57,6 +60,15 @@ public function get_title(): string {
return '';
}
+ /**
+ * Get required fields.
+ *
+ * @return array Fields required.
+ */
+ public function get_required_fields(): array {
+ return array('sqq.pinned AS pinned');
+ }
+
/**
* Output the contents of this column.
*
diff --git a/classes/question/bank/studentquiz_bank_view.php b/classes/question/bank/studentquiz_bank_view.php
index 605b26fc..6af0f8fd 100755
--- a/classes/question/bank/studentquiz_bank_view.php
+++ b/classes/question/bank/studentquiz_bank_view.php
@@ -29,6 +29,8 @@
use mod_studentquiz\utils;
use stdClass;
use core_question\local\bank\question_version_status;
+use qbank_managecategories\category_condition;
+use core_question\local\bank\column_manager_base;
defined('MOODLE_INTERNAL') || die();
@@ -42,15 +44,15 @@
require_once(__DIR__ . '/attempts_column.php');
require_once(__DIR__ . '/comments_column.php');
require_once(__DIR__ . '/state_column.php');
+require_once(__DIR__ . '/state_pin_column.php');
require_once(__DIR__ . '/anonym_creator_name_column.php');
-require_once(__DIR__ . '/preview_column.php');
require_once(__DIR__ . '/question_name_column.php');
-require_once(__DIR__ . '/sq_hidden_action_column.php');
-require_once(__DIR__ . '/sq_edit_action_column.php');
-require_once(__DIR__ . '/sq_pin_action_column.php');
-require_once(__DIR__ . '/state_pin_column.php');
+require_once(__DIR__ . '/sq_edit_action.php');
+require_once(__DIR__ . '/sq_preview_action.php');
+require_once(__DIR__ . '/sq_delete_action.php');
+require_once(__DIR__ . '/sq_hidden_action.php');
+require_once(__DIR__ . '/sq_pin_action.php');
require_once(__DIR__ . '/sq_edit_menu_column.php');
-require_once(__DIR__ . '/sq_delete_action_column.php');
/**
* Module instance settings form
@@ -115,7 +117,7 @@ class studentquiz_bank_view extends \core_question\local\bank\view {
/**
* @var mixed
*/
- private $pagevars;
+ protected $pagevars;
/**
* @var stdClass StudentQuiz renderer.
@@ -139,18 +141,17 @@ class studentquiz_bank_view extends \core_question\local\bank\view {
public function __construct($contexts, $pageurl, $course, $cm, $studentquiz, $pagevars, $report) {
$this->set_filter_post_data();
global $USER, $PAGE;
- $this->pagevars = $pagevars;
$this->studentquiz = $studentquiz;
$this->userid = $USER->id;
$this->report = $report;
- parent::__construct($contexts, $pageurl, $course, $cm);
+ parent::__construct($contexts, $pageurl, $course, $cm, $pagevars);
+
$this->set_filter_form_fields($this->is_anonymized());
$this->initialize_filter_form($pageurl);
$currentgroup = groups_get_activity_group($cm, true);
$this->currentgroupjoinsql = utils::groups_get_questions_joins($currentgroup, 'sqq.groupid');
// Init search conditions with filterform state.
- $categorycondition = new \core_question\bank\search\category_condition(
- $pagevars['cat'], $pagevars['recurse'], $contexts, $pageurl, $course);
+ $categorycondition = new category_condition($this);
$studentquizcondition = new \mod_studentquiz\condition\studentquiz_condition($cm, $this->filterform,
$this->report, $studentquiz);
$this->isfilteractive = $studentquizcondition->is_filter_active();
@@ -160,49 +161,23 @@ public function __construct($contexts, $pageurl, $course, $cm, $studentquiz, $pa
/**
* Shows the question bank interface.
- *
- * The function also processes a number of actions:
- *
- * Actions affecting the question pool:
- * move Moves a question to a different category
- * deleteselected Deletes the selected questions from the category
- * Other actions:
- * category Chooses the category
- * params: $tabname question bank edit tab name, for permission checking
- * $pagevars current list of page variables
- *
- * @param array $pagevars
- * @param string $tabname
*/
- public function display($pagevars, $tabname): void {
- $page = $pagevars['qpage'];
- $perpage = $pagevars['qperpage'];
- $cat = $pagevars['cat'];
- $recurse = $pagevars['recurse'];
- $showhidden = $pagevars['showhidden'];
- $showquestiontext = $pagevars['qbshowtext'];
- $tagids = [];
- if (!empty($pagevars['qtagids'])) {
- $tagids = $pagevars['qtagids'];
- }
+ public function display(): void {
$output = '';
$this->build_query();
// Get result set.
- $questions = $this->load_questions($page, $perpage);
+ $questions = $this->load_questions();
$this->questions = $questions;
- $this->countsql = count($this->questions);
- if ($this->countsql || $this->isfilteractive) {
+ if ($this->totalnumber || $this->isfilteractive) {
// We're unable to force the filter form to submit with get method. We have 2 forms on the page
// which need to interact with each other, so forcing method as get here.
$output .= str_replace('method="post"', 'method="get"', $this->renderer->render_filter_form($this->filterform));
}
echo $output;
- if ($this->countsql > 0) {
- $this->display_question_list($this->baseurl, $cat, null, $page, $perpage,
- $this->contexts->having_cap('moodle/question:add')
- );
+ if ($this->totalnumber > 0) {
+ $this->display_question_list();
} else {
list($message, $questionsubmissionallow) = mod_studentquiz_check_availability($this->studentquiz->opensubmissionfrom,
$this->studentquiz->closesubmissionfrom, 'submission');
@@ -225,11 +200,30 @@ public function get_questions() {
*/
protected function default_sort(): array {
return [
- 'mod_studentquiz\bank\anonym_creator_name_column-timecreated' => -1,
- 'mod_studentquiz\bank\question_name_column' => 1,
+ 'mod_studentquiz__bank__anonym_creator_name_column-timecreated' => SORT_DESC,
+ 'mod_studentquiz__bank__question_name_column' => SORT_ASC,
];
}
+ public function new_sort_url($sortname, $newsortreverse): string {
+ // Due to the way sorting param name change in Moodle 4.3.
+ // We need to override this so we can remove all sort params in the url.
+ // So that when we run the new_sort_url function, our sort name always be the first param in the url.
+ // Example: 4.2, we have a default sorting ['qb1' => 'columnA', 'qb2' => 'columnB'].
+ // After we run the new_sort_url function, it will return ['qb1' => 'columnC', 'qb2' => 'columnA', 'qb3' => 'columnB'].
+ // But in 4.3, each column is unique key, so we can't override the param like that.
+ // Example: ['columnA' => 3, 'columnB' => 4].
+ // We want our columnC to be move the become the first element of the sorting array.
+ // The simple way is just remove all existing sorting param in the baseurl, so when we running the new_sort_url function.
+ // It will return like this ['columnC' => 4, 'columnA' => 3, 'columnB' => 4].
+ foreach ($this->baseurl->params() as $paramname => $value) {
+ if (strpos($paramname, 'sortdata') !== false) {
+ $this->baseurl->remove_params($paramname);
+ }
+ }
+ return parent::new_sort_url($sortname, $newsortreverse);
+ }
+
/**
* Create the SQL query to retrieve the indicated questions, based on
* \core_question\local\bank\search\condition filters.
@@ -262,6 +256,7 @@ protected function build_query(): void {
'qv.status',
'q.timecreated',
'q.createdby',
+ 'qc.contextid',
];
// Only show ready and draft question.
$tests = [
@@ -289,7 +284,7 @@ protected function build_query(): void {
$sorts = array();
foreach ($this->sort as $sort => $order) {
list($colname, $subsort) = $this->parse_subsort($sort);
- $sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort);
+ $sorts[] = $this->requiredcolumns[$colname]->sort_expression($order === SORT_DESC, $subsort);
}
// Build the where clause and load params from search conditions.
@@ -307,6 +302,7 @@ protected function build_query(): void {
$sql = ' FROM {question} q ' . implode(' ', $joins);
$sql .= ' WHERE ' . implode(' AND ', $tests);
$this->sqlparams = $params;
+ $this->countsql = 'SELECT count(1)' . $sql;
$this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts);
}
@@ -329,7 +325,9 @@ public function create_new_question_form($categoryid, $canadd): void {
$output = '';
$caption = get_string('createnewquestion', 'studentquiz');
-
+ if (is_object($categoryid)) {
+ $categoryid = $categoryid->id;
+ }
if ($canadd) {
$returnurl = $this->baseurl;
$params = array(
@@ -365,27 +363,22 @@ public function create_new_question_form($categoryid, $canadd): void {
/**
* Prints the table of questions in a category with interactions
- *
- * @param \moodle_url $pageurl The URL to reload this page.
- * @param string $categoryandcontext 'categoryID,contextID'.
- * @param int $recurse Whether to include subcategories.
- * @param int $page The number of the page to be displayed
- * @param int $perpage Number of questions to show per page
- * @param array $addcontexts contexts where the user is allowed to add new questions.
*/
- protected function display_question_list($pageurl, $categoryandcontext, $recurse = 1, $page = 0,
- $perpage = 100, $addcontexts = []): void {
+ public function display_question_list(): void {
$output = '';
- $category = $this->get_current_category($categoryandcontext);
- list($categoryid, $contextid) = explode(',', $categoryandcontext);
+ [$categoryid, $contextid] = category_condition::validate_category_param($this->pagevars['cat']);
+ $category = category_condition::get_category_record($categoryid, $contextid);
$catcontext = \context::instance_by_id($contextid);
+ $page = $this->get_pagevars('qpage');
+ $perpage = $this->get_pagevars('qperpage');
+ $addcontexts = $this->contexts->having_cap('moodle/question:add');
$output .= \html_writer::start_tag('fieldset', array('class' => 'invisiblefieldset', 'style' => 'display:block;'));
$output .= $this->renderer->render_hidden_field($this->cm->id, $this->baseurl, $perpage);
$output .= $this->renderer->render_control_buttons($catcontext, $this->has_questions_in_category(),
- $addcontexts, $category);
+ $addcontexts, $category, $this->get_pagevars('filter'));
$output .= $this->renderer->render_pagination_bar($this->pagevars, $this->baseurl, $this->totalnumber, $page,
$perpage, true);
@@ -396,7 +389,7 @@ protected function display_question_list($pageurl, $categoryandcontext, $recurse
$perpage, false);
$output .= $this->renderer->render_control_buttons($catcontext, $this->has_questions_in_category(),
- $addcontexts, $category);
+ $addcontexts, $category, $this->get_pagevars('filter'));
$output .= \html_writer::end_tag('fieldset');
$output = $this->renderer->render_question_form($output);
@@ -572,12 +565,13 @@ private function initialize_filter_form($pageurl) {
/**
* Load question from database
- * @param int $page
- * @param int $perpage
- * @return paginated array of questions
+ *
+ * @return array array of questions
*/
- private function load_questions($page, $perpage) {
+ public function load_questions() {
global $DB;
+ $page = $this->get_pagevars('qpage');
+ $perpage = $this->get_pagevars('qperpage');
$rs = $DB->get_recordset_sql($this->loadsql, $this->sqlparams);
$counterquestions = 0;
@@ -678,4 +672,31 @@ protected function wanted_columns(): array {
$this->requiredcolumns = $renderer->get_columns_for_question_bank_view($this);
return $this->requiredcolumns;
}
+
+ /**
+ * Allow qbank plugins to override the column manager.
+ *
+ * If multiple qbank plugins define a column manager, this will pick the first one sorted alphabetically.
+ *
+ * @return void
+ */
+ protected function init_column_manager(): void {
+ $this->columnmanager = new column_manager_base();
+ }
+
+ /**
+ * Initialise list of menu actions specific for SQ.
+ *
+ * @return void
+ */
+ protected function init_question_actions(): void {
+ $this->questionactions = [
+ new \mod_studentquiz\bank\sq_edit_action($this),
+ new \mod_studentquiz\bank\sq_preview_action($this),
+ new \mod_studentquiz\bank\sq_delete_action($this),
+ new \mod_studentquiz\bank\sq_hidden_action($this),
+ new \mod_studentquiz\bank\sq_pin_action($this),
+ ];
+
+ }
}
diff --git a/classes/question/bank/studentquiz_column_base.php b/classes/question/bank/studentquiz_column_base.php
index c81119db..a7028e95 100644
--- a/classes/question/bank/studentquiz_column_base.php
+++ b/classes/question/bank/studentquiz_column_base.php
@@ -51,4 +51,14 @@ public function display($question, $rowclasses): void {
public function get_extra_classes():array {
return $this->extraclasses;
}
+
+ /**
+ * Required columns
+ *
+ * @return array fields required. use table alias 'q' for the question table, or one of the
+ * ones from get_extra_joins. Every field requested must specify a table prefix.
+ */
+ public function get_required_fields(): array {
+ return ['sqq.hidden AS sq_hidden'];
+ }
}
diff --git a/classes/question/bank/tag_column.php b/classes/question/bank/tag_column.php
index ac330eac..636257f6 100644
--- a/classes/question/bank/tag_column.php
+++ b/classes/question/bank/tag_column.php
@@ -47,6 +47,9 @@ class tag_column extends studentquiz_column_base {
*/
protected $renderer;
+ /** @var int category id */
+ protected $categoryid;
+
/**
* Initialise Parameters for join
*/
diff --git a/classes/utils.php b/classes/utils.php
index 9c39a8b3..34f99aef 100644
--- a/classes/utils.php
+++ b/classes/utils.php
@@ -497,9 +497,10 @@ public static function mark_question_comment_current_active_tab(&$tabs, $private
if (!$found) {
$tabs[0]['active'] = true;
}
-
- // Allow user to update user preference via ajax.
- user_preference_allow_ajax_update(self::USER_PREFERENCE_QUESTION_ACTIVE_TAB, PARAM_TEXT);
+ if (self::moodle_version_is("<=", "42")) {
+ // Allow user to update user preference via ajax.
+ user_preference_allow_ajax_update(self::USER_PREFERENCE_QUESTION_ACTIVE_TAB, PARAM_TEXT);
+ }
}
/**
@@ -556,7 +557,7 @@ public static function can_view_state_history($cmid, $question) {
public static function get_question_state($studentquizquestion) {
global $DB;
- return $DB->get_field('studentquiz_question', 'state', ['id' => $studentquizquestion->id]);
+ return $DB->get_field('studentquiz_question', 'state', ['id' => $studentquizquestion->get_id()]);
}
/**
diff --git a/lib.php b/lib.php
index d5d778cc..af1b66c6 100755
--- a/lib.php
+++ b/lib.php
@@ -658,3 +658,18 @@ function studentquiz_questions_in_use(array $questionids): bool {
return question_engine::questions_in_use($questionids,
new qubaid_join('{studentquiz_attempt} sa', 'sa.questionusageid'));
}
+
+/**
+ * Implements callback user_preferences, lists preferences that users are allowed to update directly
+ *
+ * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
+ *
+ * @return array
+ */
+function mod_studentquiz_user_preferences() {
+ return [utils::USER_PREFERENCE_QUESTION_ACTIVE_TAB => [
+ 'type' => PARAM_TEXT,
+ 'null' => NULL_NOT_ALLOWED,
+ 'default' => utils::COMMENT_TYPE_PRIVATE]
+ ];
+}
diff --git a/renderer.php b/renderer.php
index 55cefe20..94fa02fe 100755
--- a/renderer.php
+++ b/renderer.php
@@ -524,7 +524,9 @@ public function render_difficulty_level_column($question, $rowclasses) {
$mydifficultytitle = get_string('mydifficulty_column_name', 'studentquiz');
$title = "";
if (!empty($question->difficultylevel) || !empty($question->mydifficulty)) {
- $title = $difficultytitle . ': ' . (100 * round($question->difficultylevel, 2)) . '% ';
+ if (!empty($question->difficultylevel)) {
+ $title = $difficultytitle . ': ' . (100 * round($question->difficultylevel, 2)) . '% ';
+ }
if (!empty($question->mydifficulty)) {
$title .= ', ' . $mydifficultytitle . ': ' . (100 * round($question->mydifficulty, 2)) . '%';
} else {
@@ -919,6 +921,30 @@ public function render_report_more_link($url) {
* @return array
*/
public function get_columns_for_question_bank_view(mod_studentquiz\question\bank\studentquiz_bank_view $view) {
+ return [
+ new core_question\local\bank\checkbox_column($view),
+ new qbank_viewquestiontype\question_type_column($view),
+ new \mod_studentquiz\bank\state_column($view),
+ new \mod_studentquiz\bank\state_pin_column($view),
+ new \mod_studentquiz\bank\question_name_column($view),
+ new \mod_studentquiz\bank\sq_edit_menu_column($view),
+ new qbank_history\version_number_column($view),
+ new \mod_studentquiz\bank\anonym_creator_name_column($view),
+ new \mod_studentquiz\bank\tag_column($view),
+ new \mod_studentquiz\bank\attempts_column($view),
+ new \mod_studentquiz\bank\difficulty_level_column($view),
+ new \mod_studentquiz\bank\rate_column($view),
+ new \mod_studentquiz\bank\comment_column($view),
+ ];
+ }
+
+ /**
+ * Get all the required columns for StudentQuiz view.
+ *
+ * @param mod_studentquiz\question\bank\studentquiz_bank_view_pre_43 $view
+ * @return array
+ */
+ public function get_columns_for_question_bank_view_pre_43(mod_studentquiz\question\bank\studentquiz_bank_view_pre_43 $view) {
return [
new core_question\local\bank\checkbox_column($view),
new qbank_viewquestiontype\question_type_column($view),
@@ -930,7 +956,7 @@ public function get_columns_for_question_bank_view(mod_studentquiz\question\bank
new \mod_studentquiz\bank\sq_delete_action_column($view),
new \mod_studentquiz\bank\sq_hidden_action_column($view),
new \mod_studentquiz\bank\sq_pin_action_column($view),
- new \mod_studentquiz\bank\sq_edit_menu_column($view),
+ new \mod_studentquiz\bank\sq_edit_menu_column_pre_43($view),
new qbank_history\version_number_column($view),
new \mod_studentquiz\bank\anonym_creator_name_column($view),
new \mod_studentquiz\bank\tag_column($view),
@@ -1198,9 +1224,10 @@ function createBoltBar(mine, average) {
* @param bool $hasquestionincategory
* @param mixed $addcontexts
* @param stdClass $category
+ * @param array $filter
* @return string
*/
- public function render_control_buttons($catcontext, $hasquestionincategory, $addcontexts, $category) {
+ public function render_control_buttons($catcontext, $hasquestionincategory, $addcontexts, $category, array $filter = []) {
global $COURSE;
$output = '';
@@ -1216,8 +1243,16 @@ public function render_control_buttons($catcontext, $hasquestionincategory, $add
$studentquiz->openansweringfrom, $studentquiz->closeansweringfrom, 'answering');
$deleteurl = new \moodle_url('/question/bank/deletequestion/delete.php', ['courseid' => $COURSE->id,
'returnurl' => $this->page->url]);
+
+ // Due to Moodle 4.3 changes.
+ // We need a filter param in moveurl.
+ $returnmoveurl = $this->page->url;
+ if ($filter) {
+ $returnmoveurl->param('filter', json_encode($filter));
+ }
$movetourl = new \moodle_url('/question/bank/bulkmove/move.php', ['courseid' => $COURSE->id,
- 'returnurl' => $this->page->url]);
+ 'returnurl' => $returnmoveurl]);
+
$changestateurl = new \moodle_url('/mod/studentquiz/changestate.php', ['courseid' => $COURSE->id,
'returnurl' => $this->page->url]);
if ($hasquestionincategory) {
diff --git a/reportlib.php b/reportlib.php
index c777d8bc..b5d135b8 100755
--- a/reportlib.php
+++ b/reportlib.php
@@ -98,6 +98,8 @@ public function get_enrolled_users() {
/** @var stdClass */
protected $studentquizstats;
+ protected $questionstats;
+
/**
* Overall Stats of the studentquiz
* @return stdClass
diff --git a/styles.css b/styles.css
index d690ebe3..6e4e4b56 100644
--- a/styles.css
+++ b/styles.css
@@ -194,6 +194,25 @@
font-weight: normal;
}
+.path-mod-studentquiz #categoryquestions th {
+ font-weight: 400;
+}
+
+.path-mod-studentquiz #categoryquestions td {
+ vertical-align: inherit;
+}
+
+.path-mod-studentquiz #categoryquestions td,
+.path-mod-studentquiz #categoryquestions th {
+ padding: 0.2em;
+}
+
+.path-mod-studentquiz .question-bank-table td.modifiername span.date,
+.path-mod-studentquiz .question-bank-table td.creatorname span.date {
+ font-weight: normal;
+ font-size: 0.8em;
+}
+
.path-mod-studentquiz #categoryquestions td.questionname,
.path-mod-studentquiz #categoryquestions td.creatorname {
white-space: normal;
diff --git a/tests/bank_performance_test.php b/tests/bank_performance_test.php
index a0631a45..23977224 100644
--- a/tests/bank_performance_test.php
+++ b/tests/bank_performance_test.php
@@ -17,11 +17,17 @@
namespace mod_studentquiz;
use mod_studentquiz\question\bank\studentquiz_bank_view;
+use mod_studentquiz\question\bank\studentquiz_bank_view_pre_43;
+use mod_studentquiz\utils;
defined('MOODLE_INTERNAL') || die();
global $CFG;
-require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/studentquiz_bank_view.php');
+if (utils::moodle_version_is("<=", "42")) {
+ require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php');
+} else {
+ require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/studentquiz_bank_view.php');
+}
require_once($CFG->dirroot . '/mod/studentquiz/reportlib.php');
require_once($CFG->dirroot . '/lib/questionlib.php');
require_once($CFG->dirroot . '/question/editlib.php');
@@ -85,10 +91,17 @@ public function run_questionbank($result) {
);
$report = new mod_studentquiz_report($result['cm']->id);
- $questionbank = new studentquiz_bank_view(
- new \core_question\local\bank\question_edit_contexts(\context_module::instance($result['cm']->id)),
- new moodle_url('/mod/studentquiz/view.php', array('cmid' => $result['cm']->id)),
- $result['course'], $result['cm'], $result['studentquiz'], $pagevars, $report);
+ if (utils::moodle_version_is("<=", "42")) {
+ $questionbank = new studentquiz_bank_view_pre_43(
+ new \core_question\local\bank\question_edit_contexts(\context_module::instance($result['cm']->id)),
+ new moodle_url('/mod/studentquiz/view.php', array('cmid' => $result['cm']->id)),
+ $result['course'], $result['cm'], $result['studentquiz'], $pagevars, $report);
+ } else {
+ $questionbank = new studentquiz_bank_view(
+ new \core_question\local\bank\question_edit_contexts(\context_module::instance($result['cm']->id)),
+ new moodle_url('/mod/studentquiz/view.php', array('cmid' => $result['cm']->id)),
+ $result['course'], $result['cm'], $result['studentquiz'], $pagevars, $report);
+ }
return $questionbank;
}
diff --git a/tests/bank_view_test.php b/tests/bank_view_test.php
index ead48a9e..856d03bc 100644
--- a/tests/bank_view_test.php
+++ b/tests/bank_view_test.php
@@ -18,11 +18,16 @@
use mod_studentquiz\local\studentquiz_question;
use mod_studentquiz\question\bank\studentquiz_bank_view;
+use mod_studentquiz\question\bank\studentquiz_bank_view_pre_43;
defined('MOODLE_INTERNAL') || die();
global $CFG;
-require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/studentquiz_bank_view.php');
+if (utils::moodle_version_is("<=", "42")) {
+ require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php');
+} else {
+ require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/studentquiz_bank_view.php');
+}
require_once($CFG->dirroot . '/mod/studentquiz/reportlib.php');
require_once($CFG->dirroot . '/lib/questionlib.php');
require_once($CFG->dirroot . '/question/editlib.php');
@@ -107,16 +112,29 @@ public function run_questionbank() {
'cat' => $this->cat->id . ',' . $this->ctx->id,
'showall' => 0,
'showallprinted' => 0,
+ 'tabname' => 'questions',
+ 'qperpage' => 100,
+ 'qpage' => 0,
);
$report = new \mod_studentquiz_report($this->cm->id);
- $questionbank = new studentquiz_bank_view(
+ if (utils::moodle_version_is("<=", "42")) {
+ $questionbank = new studentquiz_bank_view_pre_43(
new \core_question\local\bank\question_edit_contexts(\context_module::instance($this->cm->id))
, new \moodle_url('/mod/studentquiz/view.php', array('cmid' => $this->cm->id))
, $this->course
, $this->cm
, $this->studentquiz
, $pagevars, $report);
+ } else {
+ $questionbank = new studentquiz_bank_view(
+ new \core_question\local\bank\question_edit_contexts(\context_module::instance($this->cm->id))
+ , new \moodle_url('/mod/studentquiz/view.php', array('cmid' => $this->cm->id))
+ , $this->course
+ , $this->cm
+ , $this->studentquiz
+ , $pagevars, $report);
+ }
return $questionbank;
}
@@ -154,29 +172,49 @@ public function test_wanted_columns() {
$this->resetAfterTest(true);
$questionbank = $this->run_questionbank();
- $reflector = new \ReflectionClass('mod_studentquiz\question\bank\studentquiz_bank_view');
+ $below42 = utils::moodle_version_is("<=", "42");
+ if ($below42) {
+ $reflector = new \ReflectionClass('mod_studentquiz\question\bank\studentquiz_bank_view_pre_43');
+ } else {
+ $reflector = new \ReflectionClass('mod_studentquiz\question\bank\studentquiz_bank_view');
+ }
$method = $reflector->getMethod('wanted_columns');
$method->setAccessible(true);
$requiredcolumns = $method->invokeArgs($questionbank, [$questionbank]);
-
- $this->assertInstanceOf('core_question\local\bank\checkbox_column', $requiredcolumns[0]);
- $this->assertInstanceOf('qbank_viewquestiontype\question_type_column', $requiredcolumns[1]);
- $this->assertInstanceOf('mod_studentquiz\bank\state_column', $requiredcolumns[2]);
- $this->assertInstanceOf('mod_studentquiz\bank\state_pin_column', $requiredcolumns[3]);
- $this->assertInstanceOf('mod_studentquiz\bank\question_name_column', $requiredcolumns[4]);
- $this->assertInstanceOf('mod_studentquiz\bank\sq_edit_action_column', $requiredcolumns[5]);
- $this->assertInstanceOf('mod_studentquiz\bank\preview_column', $requiredcolumns[6]);
- $this->assertInstanceOf('mod_studentquiz\bank\sq_delete_action_column', $requiredcolumns[7]);
- $this->assertInstanceOf('mod_studentquiz\bank\sq_hidden_action_column', $requiredcolumns[8]);
- $this->assertInstanceOf('mod_studentquiz\bank\sq_pin_action_column', $requiredcolumns[9]);
- $this->assertInstanceOf('mod_studentquiz\bank\sq_edit_menu_column', $requiredcolumns[10]);
- $this->assertInstanceOf('qbank_history\version_number_column', $requiredcolumns[11]);
- $this->assertInstanceOf('mod_studentquiz\bank\anonym_creator_name_column', $requiredcolumns[12]);
- $this->assertInstanceOf('mod_studentquiz\bank\tag_column', $requiredcolumns[13]);
- $this->assertInstanceOf('mod_studentquiz\bank\attempts_column', $requiredcolumns[14]);
- $this->assertInstanceOf('mod_studentquiz\bank\difficulty_level_column', $requiredcolumns[15]);
- $this->assertInstanceOf('mod_studentquiz\bank\rate_column', $requiredcolumns[16]);
- $this->assertInstanceOf('mod_studentquiz\bank\comment_column', $requiredcolumns[17]);
+ if ($below42) {
+ $this->assertInstanceOf('core_question\local\bank\checkbox_column', $requiredcolumns[0]);
+ $this->assertInstanceOf('qbank_viewquestiontype\question_type_column', $requiredcolumns[1]);
+ $this->assertInstanceOf('mod_studentquiz\bank\state_column', $requiredcolumns[2]);
+ $this->assertInstanceOf('mod_studentquiz\bank\state_pin_column', $requiredcolumns[3]);
+ $this->assertInstanceOf('mod_studentquiz\bank\question_name_column', $requiredcolumns[4]);
+ $this->assertInstanceOf('mod_studentquiz\bank\sq_edit_action_column', $requiredcolumns[5]);
+ $this->assertInstanceOf('mod_studentquiz\bank\preview_column', $requiredcolumns[6]);
+ $this->assertInstanceOf('mod_studentquiz\bank\sq_delete_action_column', $requiredcolumns[7]);
+ $this->assertInstanceOf('mod_studentquiz\bank\sq_hidden_action_column', $requiredcolumns[8]);
+ $this->assertInstanceOf('mod_studentquiz\bank\sq_pin_action_column', $requiredcolumns[9]);
+ $this->assertInstanceOf('mod_studentquiz\bank\sq_edit_menu_column_pre_43', $requiredcolumns[10]);
+ $this->assertInstanceOf('qbank_history\version_number_column', $requiredcolumns[11]);
+ $this->assertInstanceOf('mod_studentquiz\bank\anonym_creator_name_column', $requiredcolumns[12]);
+ $this->assertInstanceOf('mod_studentquiz\bank\tag_column', $requiredcolumns[13]);
+ $this->assertInstanceOf('mod_studentquiz\bank\attempts_column', $requiredcolumns[14]);
+ $this->assertInstanceOf('mod_studentquiz\bank\difficulty_level_column', $requiredcolumns[15]);
+ $this->assertInstanceOf('mod_studentquiz\bank\rate_column', $requiredcolumns[16]);
+ $this->assertInstanceOf('mod_studentquiz\bank\comment_column', $requiredcolumns[17]);
+ } else {
+ $this->assertInstanceOf('core_question\local\bank\checkbox_column', $requiredcolumns[0]);
+ $this->assertInstanceOf('qbank_viewquestiontype\question_type_column', $requiredcolumns[1]);
+ $this->assertInstanceOf('mod_studentquiz\bank\state_column', $requiredcolumns[2]);
+ $this->assertInstanceOf('mod_studentquiz\bank\state_pin_column', $requiredcolumns[3]);
+ $this->assertInstanceOf('mod_studentquiz\bank\question_name_column', $requiredcolumns[4]);
+ $this->assertInstanceOf('\mod_studentquiz\bank\sq_edit_menu_column', $requiredcolumns[5]);
+ $this->assertInstanceOf('qbank_history\version_number_column', $requiredcolumns[6]);
+ $this->assertInstanceOf('mod_studentquiz\bank\anonym_creator_name_column', $requiredcolumns[7]);
+ $this->assertInstanceOf('mod_studentquiz\bank\tag_column', $requiredcolumns[8]);
+ $this->assertInstanceOf('mod_studentquiz\bank\attempts_column', $requiredcolumns[9]);
+ $this->assertInstanceOf('mod_studentquiz\bank\difficulty_level_column', $requiredcolumns[10]);
+ $this->assertInstanceOf('mod_studentquiz\bank\rate_column', $requiredcolumns[11]);
+ $this->assertInstanceOf('mod_studentquiz\bank\comment_column', $requiredcolumns[12]);
+ }
}
/**
@@ -206,7 +244,7 @@ protected function create_random_questions($count, $userid) {
protected function create_rate($sqq, $userid) {
$raterecord = new \stdClass();
$raterecord->rate = 5;
- $raterecord->studentquizquestionid = $sqq->id;
+ $raterecord->studentquizquestionid = $sqq->get_id();
$raterecord->userid = $userid;
}
@@ -217,7 +255,7 @@ protected function create_rate($sqq, $userid) {
*/
protected function create_comment($sqq, $userid) {
$commentrecord = new \stdClass();
- $commentrecord->studentquizquestionid = $sqq->id;
+ $commentrecord->studentquizquestionid = $sqq->get_id();
$commentrecord->userid = $userid;
$this->studentquizgenerator->create_comment($commentrecord);
diff --git a/tests/behat/backup.feature b/tests/behat/backup.feature
index d906e4b1..7539b247 100644
--- a/tests/behat/backup.feature
+++ b/tests/behat/backup.feature
@@ -10,7 +10,8 @@ Feature: Restore specific studentquiz old backup to test UI feature
@javascript @_file_upload
Scenario: Restore moodle backups containing history comments.
Given I am on the "Course 1" "restore" page logged in as "admin"
- And I press "Manage backup files"
+ # Main branch has change the text to "Manage course backups" so we should use xpath.
+ And I click on "(//*[@class='singlebutton']//button)[1]" "xpath_element"
And I upload "mod/studentquiz/tests/fixtures/backup-moodle311-c1-historycomment.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "backup-moodle311-c1-historycomment.mbz" backup into a new course using this options:
@@ -31,7 +32,8 @@ Feature: Restore specific studentquiz old backup to test UI feature
@javascript @_file_upload @_switch_window
Scenario: Restore moodle backups containing old StudentQuiz activity without state history table.
Given I am on the "Course 1" "restore" page logged in as "admin"
- And I press "Manage backup files"
+ # Main branch has change the text to "Manage course backups" so we should use xpath.
+ And I click on "(//*[@class='singlebutton']//button)[1]" "xpath_element"
And I upload "mod/studentquiz/tests/fixtures/backup-moodle2-course-3-sqo-20211011-missing_state_history.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "backup-moodle2-course-3-sqo-20211011-missing_state_history.mbz" backup into a new course using this options:
diff --git a/tests/behat/backup_restore_availability_setting.feature b/tests/behat/backup_restore_availability_setting.feature
index c1df4e2d..75b4a6bf 100644
--- a/tests/behat/backup_restore_availability_setting.feature
+++ b/tests/behat/backup_restore_availability_setting.feature
@@ -14,7 +14,8 @@ Feature: Backup and restore activity studentquiz
@javascript @_file_upload
Scenario: Restore moodle backups containing old StudentQuiz activity has availability and question publishing setting.
When I am on the "Course 1" "restore" page logged in as "admin"
- And I press "Manage backup files"
+ # Main branch has change the text to "Manage course backups" so we should use xpath.
+ And I click on "(//*[@class='singlebutton']//button)[1]" "xpath_element"
And I upload "mod/studentquiz/tests/fixtures/backup-moodle2-availability-setting.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "backup-moodle2-availability-setting.mbz" backup into a new course using this options:
diff --git a/tests/behat/custom_completion.feature b/tests/behat/custom_completion.feature
index bebbc194..0f44670f 100644
--- a/tests/behat/custom_completion.feature
+++ b/tests/behat/custom_completion.feature
@@ -21,15 +21,9 @@ Feature: Set a studentquiz to be marked complete when the student meets the cond
@javascript
Scenario: Check studentquiz mark done when the student meets the conditions of the completion point
- Given I add a "StudentQuiz" to section "1"
- When I set the following fields to these values:
- | StudentQuiz Name | StudentQuiz 1 |
- | Description | Test studentquiz description |
- | completion | 2 |
- | completionpointenabled | 1 |
- | completionpoint | 10 |
- And I press "Save and display"
- And I log out
+ Given the following "activities" exist:
+ | course | activity | name | intro | completion | completionpointenabled | completionpoint | publishnewquestion | questionquantifier |
+ | C1 | studentquiz | StudentQuiz 1 | Test studentquiz description | 2 | 1 | 10 | 1 | 10 |
# Create owned question by student role.
And I am on the "StudentQuiz 1" "mod_studentquiz > View" page logged in as "student1"
And I click on "Create new question" "button"
@@ -43,15 +37,9 @@ Feature: Set a studentquiz to be marked complete when the student meets the cond
@javascript
Scenario: Check studentquiz mark done when the student meets the conditions of the completion created
- Given I add a "StudentQuiz" to section "1"
- When I set the following fields to these values:
- | StudentQuiz Name | StudentQuiz 1 |
- | Description | Test studentquiz description |
- | completion | 2 |
- | completionquestionpublishedenabled | 1 |
- | completionquestionpublished | 2 |
- And I press "Save and display"
- And I log out
+ Given the following "activities" exist:
+ | course | activity | name | intro | completion | completionquestionpublishedenabled | completionquestionpublished | publishnewquestion |
+ | C1 | studentquiz | StudentQuiz 1 | Test studentquiz description | 2 | 1 | 2 | 1 |
# Create owned question by student role.
And I am on the "StudentQuiz 1" "mod_studentquiz > View" page logged in as "student1"
And I click on "Create new question" "button"
@@ -73,16 +61,9 @@ Feature: Set a studentquiz to be marked complete when the student meets the cond
@javascript
Scenario: Check studentquiz mark done when the student meets the conditions of the completion created approved
- Given I add a "StudentQuiz" to section "1"
- When I set the following fields to these values:
- | StudentQuiz Name | StudentQuiz 1 |
- | Description | Test studentquiz description |
- | completion | 2 |
- | publishnewquestion | 1 |
- | completionquestionapprovedenabled | 1 |
- | completionquestionapproved | 1 |
- And I press "Save and display"
- And I log out
+ Given the following "activities" exist:
+ | course | activity | name | intro | completion | completionquestionapprovedenabled | completionquestionapproved | publishnewquestion | approvedquantifier |
+ | C1 | studentquiz | StudentQuiz 1 | Test studentquiz description | 2 | 1 | 1 | 1 | 5 |
# Create owned question by student role.
And I am on the "StudentQuiz 1" "mod_studentquiz > View" page logged in as "student1"
And I click on "Create new question" "button"
diff --git a/tests/behat/navigation.feature b/tests/behat/navigation.feature
index 630f418e..63e2ced9 100644
--- a/tests/behat/navigation.feature
+++ b/tests/behat/navigation.feature
@@ -55,7 +55,6 @@ Feature: Navigation to the pages
And I should see "Categories"
And I should see "Import"
And I should see "Export"
- And I should see "Select a category:"
Scenario: Check that the More link exist in My Progress and Ranking block
When I navigate to "StudentQuiz" in current page administration
diff --git a/tests/behat/state_visibility_old_backup_import.feature b/tests/behat/state_visibility_old_backup_import.feature
index b79699cf..53ddb482 100644
--- a/tests/behat/state_visibility_old_backup_import.feature
+++ b/tests/behat/state_visibility_old_backup_import.feature
@@ -13,7 +13,8 @@ Feature: Restore of studentquizzes in moodle exports contain old approved column
@javascript @_file_upload
Scenario: Restore moodle backups containing old StudentQuiz activity with old approved column
When I am on the "Course 1" "restore" page logged in as "admin"
- And I press "Manage backup files"
+ # Main branch has change the text to "Manage course backups" so we should use xpath.
+ And I click on "(//*[@class='singlebutton']//button)[1]" "xpath_element"
And I upload "mod/studentquiz/tests/fixtures/backup-moodle2-aggregated-before.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "backup-moodle2-aggregated-before.mbz" backup into a new course using this options:
diff --git a/tests/comment_test.php b/tests/comment_test.php
index 5e549271..db1e0ed9 100644
--- a/tests/comment_test.php
+++ b/tests/comment_test.php
@@ -213,9 +213,9 @@ public function test_create_root_comment() {
// Create root comment.
$sqq1 = $this->studentquizquestions[0];
$text = 'Root comment';
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text);
$this->assertEquals($text, $comment->content);
- $this->assertEquals($sqq1->id, $comment->studentquizquestionid);
+ $this->assertEquals($sqq1->get_id(), $comment->studentquizquestionid);
$this->assertEquals($this->rootid, $comment->parentid);
}
@@ -227,12 +227,12 @@ public function test_create_reply_comment() {
$sqq1 = $this->studentquizquestions[0];
$text = 'Root comment';
$textreply = 'Reply root comment';
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text);
- $reply = $this->create_comment($comment->id, $sqq1->id, $textreply);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text);
+ $reply = $this->create_comment($comment->id, $sqq1->get_id(), $textreply);
// Check text reply.
$this->assertEquals($textreply, $reply->content);
// Check question id.
- $this->assertEquals($sqq1->id, $reply->studentquizquestionid);
+ $this->assertEquals($sqq1->get_id(), $reply->studentquizquestionid);
// Check if reply belongs to comment.
$this->assertEquals($comment->id, $reply->parentid);
}
@@ -248,7 +248,7 @@ public function test_create_reply_comment() {
*/
public function test_shorten_comment(string $content, string $expected, int $expectedlength): void {
$sq1 = $this->studentquizquestions[0];
- $comment = $this->create_comment($this->rootid, $sq1->id, $content);
+ $comment = $this->create_comment($this->rootid, $sq1->get_id(), $content);
$this->assertEquals($expectedlength, strlen($comment->shortcontent));
$this->assertEquals($expected, $comment->shortcontent);
}
@@ -291,7 +291,7 @@ public function test_delete_comment() {
$sqq1 = $this->studentquizquestions[0];
$text = 'Root comment';
// Dont need to convert to use delete.
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text, false);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text, false);
// Try to delete.
$comment->delete();
// Get new data.
@@ -312,9 +312,9 @@ public function test_fetch_all_comments() {
$text = 'Root comment';
$textreply = 'Reply root comment';
$numreplies = 3;
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text);
for ($i = 0; $i < $numreplies; $i++) {
- $this->create_comment($comment->id, $sqq1->id, $textreply);
+ $this->create_comment($comment->id, $sqq1->get_id(), $textreply);
}
$comments = $this->commentarea->fetch_all(0);
$data = [];
@@ -342,7 +342,7 @@ public function test_report_feature() {
global $DB;
$sqq1 = $this->studentquizquestions[0];
// Need to use comment class functions. Don't use convert to response data.
- $comment = $this->create_comment($this->rootid, $sqq1->id, 'Test comment', false);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), 'Test comment', false);
// Assume that we didn't input any emails for report. It will return false.
$this->assertFalse($comment->can_report());
// Turn on report.
@@ -377,7 +377,7 @@ private function generate_comment_list_for_sort() {
'parentid' => $this->rootid,
'userid' => $user->id,
'created' => $k + 1,
- 'studentquizquestionid' => $sqq2->id
+ 'studentquizquestionid' => $sqq2->get_id()
];
}
$DB->insert_records('studentquiz_comment', $records);
@@ -548,7 +548,7 @@ private function seed_studentquiz_period_setting($period) {
'parentid' => $this->rootid,
'userid' => $commentarea->get_user()->id,
'created' => time(),
- 'studentquizquestionid' => $sqq1->id
+ 'studentquizquestionid' => $sqq1->get_id()
]);
return $commentarea;
}
@@ -562,7 +562,7 @@ public function test_edit_comment() {
$sqq1 = $this->studentquizquestions[0];
$text = 'Root comment';
// Dont need to convert to use delete.
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text, false);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text, false);
$formdata = new \stdClass();
$formdata->message['text'] = 'Edited comment';
$formdata->type = utils::COMMENT_TYPE_PUBLIC;
@@ -619,7 +619,7 @@ public function test_create_comment_history() {
// Create root comment.
$sqq1 = $this->studentquizquestions[0];
$text = 'Root comment for history';
- $comment = $this->create_comment($this->rootid, $sqq1->id, $text, false);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), $text, false);
$comparestr = 'comment' . $comment->get_id();
$historyid = $comment->create_history($comment->get_id(), $comment->get_user_id(), 0, $comparestr);
$history = $DB->get_record('studentquiz_comment_history', ['id' => $historyid]);
@@ -634,7 +634,7 @@ public function test_create_comment_history() {
*/
public function test_get_histories() {
$sqq1 = $this->studentquizquestions[0];
- $comment = $this->create_comment($this->rootid, $sqq1->id, 'demo content', false);
+ $comment = $this->create_comment($this->rootid, $sqq1->get_id(), 'demo content', false);
$comment->create_history($comment->get_id(), $comment->get_user_id(), 1, 'comment1' . $comment->get_id());
$comment->create_history($comment->get_id(), $comment->get_user_id(), 1, 'comment2' . $comment->get_id());
$histories = $this->commentarea->get_history($comment->get_id());
diff --git a/tests/cron_test.php b/tests/cron_test.php
index 2bacaabe..ffa89f61 100644
--- a/tests/cron_test.php
+++ b/tests/cron_test.php
@@ -107,14 +107,14 @@ protected function setUp(): void {
$this->studentquizquestions[1] = studentquiz_question::get_studentquiz_question_from_question($this->questions[1]);
// Prepare comment.
$commentrecord = new \stdClass();
- $commentrecord->studentquizquestionid = $this->studentquizquestions[0]->id;
+ $commentrecord->studentquizquestionid = $this->studentquizquestions[0]->get_id();
$commentrecord->userid = $this->student1->id;
$this->getDataGenerator()->get_plugin_generator('mod_studentquiz')->create_comment($commentrecord);
// Prepare rate.
$raterecord = new \stdClass();
$raterecord->rate = 5;
- $raterecord->studentquizquestionid = $this->studentquizquestions[0]->id;
+ $raterecord->studentquizquestionid = $this->studentquizquestions[0]->get_id();
$raterecord->userid = $this->student1->id;
\mod_studentquiz\utils::save_rate($raterecord);
}
@@ -337,19 +337,19 @@ public function test_delete_orphaned_questions(): void {
$this->assertEquals(0, $DB->count_records('question', ['id' => $q2v1->id]));
$this->assertEquals(0, $DB->count_records('studentquiz_rate',
- ['studentquizquestionid' => $sqq->id]));
+ ['studentquizquestionid' => $sqq->get_id()]));
$this->assertEquals(0, $DB->count_records('studentquiz_comment',
- ['studentquizquestionid' => $sqq->id]));
+ ['studentquizquestionid' => $sqq->get_id()]));
$this->assertEquals(0, $DB->count_records('studentquiz_question',
- ['id' => $sqq->id]));
+ ['id' => $sqq->get_id()]));
$this->assertEquals(0, $DB->count_records('question', ['id' => $this->questions[0]->id]));
$this->assertEquals(0, $DB->count_records('studentquiz_rate',
- ['studentquizquestionid' => $this->studentquizquestions[0]->id]));
+ ['studentquizquestionid' => $this->studentquizquestions[0]->get_id()]));
$this->assertEquals(0, $DB->count_records('studentquiz_comment',
- ['studentquizquestionid' => $this->studentquizquestions[0]->id]));
+ ['studentquizquestionid' => $this->studentquizquestions[0]->get_id()]));
$this->assertEquals(0, $DB->count_records('studentquiz_question',
- ['id' => $this->studentquizquestions[0]->id]));
+ ['id' => $this->studentquizquestions[0]->get_id()]));
$this->assertEquals(1, $DB->count_records('question', ['id' => $q2v2->id]));
$this->assertEquals(1, $DB->count_records('question', ['id' => $q2v3->id]));
diff --git a/tests/delete_instance_test.php b/tests/delete_instance_test.php
index d818fa72..ed27aac0 100644
--- a/tests/delete_instance_test.php
+++ b/tests/delete_instance_test.php
@@ -60,7 +60,7 @@ public function test_delete_instance(): void {
'text' => 'Root message',
'format' => 1
],
- 'studentquizquestionid' => $sqq->id,
+ 'studentquizquestionid' => $sqq->get_id(),
'cmid' => $activity->cmid,
'replyto' => 0,
'type' => utils::COMMENT_TYPE_PUBLIC
@@ -89,13 +89,13 @@ public function test_delete_instance(): void {
$rate = (object) [
'id' => 0,
'rate' => rand(1, 5),
- 'studentquizquestionid' => $sqq->id,
+ 'studentquizquestionid' => $sqq->get_id(),
'userid' => $user->id
];
$DB->insert_record('studentquiz_rate', $rate);
$progress = (object) [
- 'studentquizquestionid' => $sqq->id,
+ 'studentquizquestionid' => $sqq->get_id(),
'userid' => $user->id,
'studentquizid' => $studentquiz->id,
'lastanswercorrect' => rand(0, 1),
@@ -114,10 +114,10 @@ public function test_delete_instance(): void {
];
$DB->insert_record('studentquiz_notification', $notification);
// Before deletion.
- self::check_sq_instance_data($sqq->id, $studentquiz->id, $commentid, 1);
+ self::check_sq_instance_data($sqq->get_id(), $studentquiz->id, $commentid, 1);
studentquiz_delete_instance($studentquiz->id);
// After deletion.
- self::check_sq_instance_data($sqq->id, $studentquiz->id, $commentid, 0);
+ self::check_sq_instance_data($sqq->get_id(), $studentquiz->id, $commentid, 0);
}
diff --git a/tests/generator_test.php b/tests/generator_test.php
index b4e49bba..a610ed8a 100644
--- a/tests/generator_test.php
+++ b/tests/generator_test.php
@@ -75,7 +75,7 @@ public function test_create_comment() {
$user = $this->getDataGenerator()->create_user();
$commentrecord = new \stdClass();
- $commentrecord->studentquizquestionid = $this->studentquizquestion->id;
+ $commentrecord->studentquizquestionid = $this->studentquizquestion->get_id();
$commentrecord->userid = $user->id;
$this->studentquizgenerator->create_comment($commentrecord);
@@ -97,7 +97,7 @@ public function test_create_rate() {
$raterecord = new \stdClass();
$raterecord->rate = 5;
- $raterecord->studentquizquestionid = $this->studentquizquestion->id;
+ $raterecord->studentquizquestionid = $this->studentquizquestion->get_id();
$raterecord->userid = $user->id;
\mod_studentquiz\utils::save_rate($raterecord);
$this->assertEquals($count + 1, $DB->count_records('studentquiz_rate'));
diff --git a/tests/privacy_test.php b/tests/privacy_test.php
index d7294838..7e95a57b 100644
--- a/tests/privacy_test.php
+++ b/tests/privacy_test.php
@@ -595,7 +595,8 @@ public function test_delete_data_for_all_users_in_context() {
list($questionsql, $questionparams) =
$DB->get_in_or_equal([$this->questions[0]->id, $this->questions[1]->id], SQL_PARAMS_NAMED);
list($sqqsql, $sqqparams) =
- $DB->get_in_or_equal([$this->studentquizquestions[0]->id, $this->studentquizquestions[1]->id], SQL_PARAMS_NAMED);
+ $DB->get_in_or_equal([$this->studentquizquestions[0]->get_id(), $this->studentquizquestions[1]->get_id()],
+ SQL_PARAMS_NAMED);
// Check all personal data belong to first context is deleted.
$this->assertFalse($DB->record_exists_sql("SELECT 1 FROM {studentquiz_question} WHERE id {$sqqsql}"
@@ -620,7 +621,8 @@ public function test_delete_data_for_all_users_in_context() {
list($questionsql, $questionparams) =
$DB->get_in_or_equal([$this->questions[2]->id, $this->questions[3]->id], SQL_PARAMS_NAMED);
list($sqqsql, $sqqparams) =
- $DB->get_in_or_equal([$this->studentquizquestions[2]->id, $this->studentquizquestions[3]->id], SQL_PARAMS_NAMED);
+ $DB->get_in_or_equal([$this->studentquizquestions[2]->get_id(), $this->studentquizquestions[3]->get_id()],
+ SQL_PARAMS_NAMED);
$this->assertTrue($DB->record_exists_sql("SELECT 1 FROM {studentquiz_question} WHERE id {$sqqsql}"
, $sqqparams));
$this->assertTrue($DB->record_exists_sql("SELECT 1 FROM {studentquiz_rate} WHERE studentquizquestionid {$sqqsql}"
@@ -752,7 +754,7 @@ public function test_get_users_in_context_rating() {
$question = self::create_question('Question', 'truefalse', $this->studentquiz[2]->categoryid, $anotheruser);
$sqq = studentquiz_question::get_studentquiz_question_from_question($question);
- $this->create_rate($sqq->id, $this->users[0]->id);
+ $this->create_rate($sqq->get_id(), $this->users[0]->id);
$userlist = new userlist($this->contexts[2], $this->component);
provider::get_users_in_context($userlist);
@@ -761,7 +763,7 @@ public function test_get_users_in_context_rating() {
$this->assertEquals([$anotheruser->id, $this->users[0]->id], $userlist->get_userids());
// Second student rate on another user question.
- $this->create_rate($sqq->id, $this->users[1]->id);
+ $this->create_rate($sqq->get_id(), $this->users[1]->id);
provider::get_users_in_context($userlist);
$this->assertCount(3, $userlist);
$this->assertEquals([$anotheruser->id, $this->users[0]->id, $this->users[1]->id ], $userlist->get_userids());
@@ -778,7 +780,7 @@ public function test_get_users_in_context_comment() {
$question = self::create_question('Question', 'truefalse', $this->studentquiz[2]->categoryid, $anotheruser);
$sqq = studentquiz_question::get_studentquiz_question_from_question($question);
- $this->create_comment($sqq->id, $this->users[0]->id);
+ $this->create_comment($sqq->get_id(), $this->users[0]->id);
$userlist = new userlist($this->contexts[2], $this->component);
provider::get_users_in_context($userlist);
@@ -787,7 +789,7 @@ public function test_get_users_in_context_comment() {
$this->assertEquals([$anotheruser->id, $this->users[0]->id], $userlist->get_userids());
// Second student comment on another user question.
- $this->create_comment($sqq->id, $this->users[1]->id);
+ $this->create_comment($sqq->get_id(), $this->users[1]->id);
provider::get_users_in_context($userlist);
$this->assertCount(3, $userlist);
$this->assertEquals([$anotheruser->id, $this->users[0]->id, $this->users[1]->id ], $userlist->get_userids());
@@ -805,7 +807,7 @@ public function test_get_users_in_context_comment_history() {
$question = self::create_question('Question', 'truefalse', $this->studentquiz[2]->categoryid, $anotheruser);
$sqq = studentquiz_question::get_studentquiz_question_from_question($question);
- $comment = $this->create_comment($sqq->id, $this->users[0]->id);
+ $comment = $this->create_comment($sqq->get_id(), $this->users[0]->id);
$this->create_comment_history($comment->id, $this->users[0]->id);
$userlist = new userlist($this->contexts[2], $this->component);
@@ -870,7 +872,7 @@ public function test_get_users_in_context_change_state() {
$question = self::create_question('Question', 'truefalse', $this->studentquiz[2]->categoryid, $anotheruser);
$sqq = studentquiz_question::get_studentquiz_question_from_question($question);
- $this->create_state_history($sqq->id, $this->users[0]->id);
+ $this->create_state_history($sqq->get_id(), $this->users[0]->id);
$userlist = new userlist($this->contexts[2], $this->component);
provider::get_users_in_context($userlist);
diff --git a/view.php b/view.php
index c6047694..792e7210 100755
--- a/view.php
+++ b/view.php
@@ -86,6 +86,11 @@
get_string('pagesize_invalid_input', 'studentquiz'),
null, \core\output\notification::NOTIFY_ERROR);
}
+ // In Moodle 4.3, we have a filter param after we move the questions, but in SQ, we don't use that, so remove the filter param.
+ if ($filter = optional_param('filter', 0, PARAM_RAW)) {
+ $baseurl->remove_params('filter');
+ redirect($baseurl);
+ }
}
$renderer = $PAGE->get_renderer('mod_studentquiz', 'overview');
diff --git a/viewlib.php b/viewlib.php
index 7209a0c5..54da39d7 100755
--- a/viewlib.php
+++ b/viewlib.php
@@ -27,6 +27,7 @@
require_once(__DIR__ . '/locallib.php');
use mod_studentquiz\local\studentquiz_question;
+use mod_studentquiz\utils;
/**
* This class loads and represents the state for the main view.
*
@@ -123,6 +124,7 @@ public function __construct($course, $context, $cm, $studentquiz, $userid, $repo
* Loads the question custom bank view.
*/
private function load_questionbank() {
+ global $CFG;
$_POST['cat'] = $this->get_category_id() . ',' . $this->get_context_id();
$params = $_GET;
// Get edit question link setup.
@@ -133,6 +135,9 @@ private function load_questionbank() {
$pagevars['cat'] = $this->get_category_id() . ',' . $this->get_context_id();
$this->pageurl = new moodle_url($thispageurl);
foreach ($params as $key => $value) {
+ if ($key === 'sortdata') {
+ continue;
+ }
if ($key == 'timecreated_sdt' || $key == 'timecreated_edt') {
$value = http_build_query($value);
}
@@ -163,8 +168,15 @@ private function load_questionbank() {
$thispageurl->remove_params('changepagesize');
}
$this->qbpagevar = array_merge($pagevars, $params);
- $this->questionbank = new \mod_studentquiz\question\bank\studentquiz_bank_view(
- $contexts, $thispageurl, $this->course, $this->cm, $this->studentquiz, $pagevars, $this->report);
+ if (utils::moodle_version_is("<=", "42")) {
+ require_once($CFG->dirroot . '/mod/studentquiz/classes/question/bank/legacy/studentquiz_bank_view_pre_43.php');
+ $this->questionbank = new \mod_studentquiz\question\bank\studentquiz_bank_view_pre_43(
+ $contexts, $thispageurl, $this->course, $this->cm, $this->studentquiz, $pagevars, $this->report);
+ } else {
+ $this->questionbank = new \mod_studentquiz\question\bank\studentquiz_bank_view(
+ $contexts, $thispageurl, $this->course, $this->cm, $this->studentquiz, $pagevars, $this->report);
+ }
+
}
/**