From f9fc355ec78a60e8657391f33d2e9bd60b9f1a53 Mon Sep 17 00:00:00 2001 From: Safat Shahin Date: Sat, 7 Aug 2021 02:03:58 +1000 Subject: [PATCH] MDL-71679 mod_quiz: Updates for new qbank api This implementation will introduct changes to the mod_quiz to implement the new qbank api for view. Major changes are introduced in the custom view and also all the legacy points to the old qbank are changed to qbank plugins where needed. It also deprecated and deletes classes and scripts which were kept not to break mod_quiz for the new api implementation. --- .../local/areas/core_question/base.php | 2 +- config-dist.php | 17 - lib/classes/event/question_created.php | 6 +- lib/classes/plugininfo/qbank.php | 5 +- lib/db/renamedclasses.php | 38 +- lib/questionlib.php | 42 +- lib/upgrade.txt | 5 + mod/quiz/attemptlib.php | 2 +- mod/quiz/classes/output/edit_renderer.php | 12 +- mod/quiz/classes/output/question_chooser.php | 2 +- .../question/bank/add_action_column.php | 19 +- .../classes/question/bank/custom_view.php | 168 +-- .../question/bank/question_name_column.php | 38 +- .../bank/question_name_text_column.php | 29 +- mod/quiz/edit.php | 2 +- mod/quiz/locallib.php | 6 +- mod/quiz/tests/behat/behat_mod_quiz.php | 7 +- .../editing_add_from_question_bank.feature | 2 +- .../tests/quiz_question_bank_view_test.php | 10 +- question/addquestion.php | 93 -- question/category_class.php | 603 +-------- question/category_form.php | 97 +- question/classes/bank/action_column_base.php | 61 - question/classes/bank/checkbox_column.php | 78 -- question/classes/bank/column_base.php | 369 ------ question/classes/bank/copy_action_column.php | 66 - question/classes/bank/creator_name_column.php | 75 -- .../classes/bank/delete_action_column.php | 82 -- question/classes/bank/edit_action_column.php | 66 - question/classes/bank/edit_menu_column.php | 102 -- .../classes/bank/export_xml_action_column.php | 64 - .../classes/bank/menu_action_column_base.php | 71 -- question/classes/bank/menuable_action.php | 57 - .../classes/bank/modifier_name_column.php | 75 -- .../classes/bank/preview_action_column.php | 84 -- .../classes/bank/question_name_column.php | 76 -- .../question_name_idnumber_tags_column.php | 90 -- question/classes/bank/question_text_row.php | 72 -- .../classes/bank/question_type_column.php | 60 - .../classes/bank/random_question_loader.php | 323 ----- question/classes/bank/row_base.php | 53 - .../bank/search/category_condition.php | 5 +- question/classes/bank/tags_action_column.php | 109 -- question/classes/bank/view.php | 1104 ----------------- question/classes/external.php | 3 +- .../classes/local/bank/edit_menu_column.php | 3 +- question/classes/local/bank/view.php | 60 +- question/classes/output/qbank_chooser.php | 108 -- .../classes/output/qbank_chooser_item.php | 59 - question/editlib.php | 164 +-- question/engine/renderer.php | 8 +- question/export_form.php | 73 +- question/exportone.php | 79 -- question/import.php | 149 --- question/import_form.php | 139 +-- question/lib.php | 3 +- question/move_form.php | 32 +- question/preview.php | 307 ----- question/previewlib.php | 201 +-- question/question.php | 346 ------ question/renderer.php | 8 +- .../type/calculated/datasetitems_form.php | 13 +- question/type/tags_form.php | 100 -- question/upgrade.txt | 54 + 64 files changed, 286 insertions(+), 5970 deletions(-) delete mode 100644 question/addquestion.php delete mode 100644 question/classes/bank/action_column_base.php delete mode 100644 question/classes/bank/checkbox_column.php delete mode 100644 question/classes/bank/column_base.php delete mode 100644 question/classes/bank/copy_action_column.php delete mode 100644 question/classes/bank/creator_name_column.php delete mode 100644 question/classes/bank/delete_action_column.php delete mode 100644 question/classes/bank/edit_action_column.php delete mode 100644 question/classes/bank/edit_menu_column.php delete mode 100644 question/classes/bank/export_xml_action_column.php delete mode 100644 question/classes/bank/menu_action_column_base.php delete mode 100644 question/classes/bank/menuable_action.php delete mode 100644 question/classes/bank/modifier_name_column.php delete mode 100644 question/classes/bank/preview_action_column.php delete mode 100644 question/classes/bank/question_name_column.php delete mode 100644 question/classes/bank/question_name_idnumber_tags_column.php delete mode 100644 question/classes/bank/question_text_row.php delete mode 100644 question/classes/bank/question_type_column.php delete mode 100644 question/classes/bank/random_question_loader.php delete mode 100644 question/classes/bank/row_base.php delete mode 100644 question/classes/bank/tags_action_column.php delete mode 100644 question/classes/bank/view.php delete mode 100644 question/classes/output/qbank_chooser.php delete mode 100644 question/classes/output/qbank_chooser_item.php delete mode 100644 question/exportone.php delete mode 100644 question/import.php delete mode 100644 question/preview.php delete mode 100644 question/question.php delete mode 100644 question/type/tags_form.php diff --git a/admin/tool/brickfield/classes/local/areas/core_question/base.php b/admin/tool/brickfield/classes/local/areas/core_question/base.php index 8887675f059..0dbf58be1b6 100644 --- a/admin/tool/brickfield/classes/local/areas/core_question/base.php +++ b/admin/tool/brickfield/classes/local/areas/core_question/base.php @@ -137,7 +137,7 @@ abstract class base extends area_base { } // Default to SITEID if courseid is null, i.e. system or category level questions. $thiscourseid = ($componentinfo->courseid !== null) ? $componentinfo->courseid : SITEID; - return new \moodle_url('/question/question.php', ['courseid' => $thiscourseid, 'id' => $questionid]); + return new \moodle_url('/question/bank/editquestion/question.php', ['courseid' => $thiscourseid, 'id' => $questionid]); } /** diff --git a/config-dist.php b/config-dist.php index 317fd9543ca..31b7dced261 100644 --- a/config-dist.php +++ b/config-dist.php @@ -692,23 +692,6 @@ $CFG->admin = 'admin'; // $CFG->uninstallclionly = true; // // -// Customise question bank display -// -// The display of Moodle's question bank is made up of a number of columns. -// You can customise this display by giving a comma-separated list of column class -// names here. Each class must be a subclass of \core_question\bank\column_base. -// For example you might define a class like -// class \local_qbank_extensions\my_column extends \core_question\bank\column_base -// in a local plugin, then add it to the list here. At the time of writing, -// the default question bank display is equivalent to the following, but you might like -// to check the latest default in question/classes/bank/view.php before setting this. -// -// $CFG->questionbankcolumns = 'checkbox_column,question_type_column,' -// . 'question_name_idnumber_tags_column,edit_menu_column,' -// . 'tags_action_column,edit_action_column,copy_action_column,' -// . 'preview_action_column,delete_action_column,export_xml_action_column,' -// . 'creator_name_column,modifier_name_column'; -// // Forum summary report // // In order for the forum summary report to calculate word count and character count data, those details are now stored diff --git a/lib/classes/event/question_created.php b/lib/classes/event/question_created.php index b05e1fb151d..feeb4edc747 100644 --- a/lib/classes/event/question_created.php +++ b/lib/classes/event/question_created.php @@ -77,11 +77,11 @@ class question_created extends question_base { public function get_url() { if ($this->courseid) { if ($this->contextlevel == CONTEXT_MODULE) { - return new \moodle_url('/question/preview.php', ['cmid' => $this->contextinstanceid, 'id' => $this->objectid]); + return new \moodle_url('/question/bank/previewquestion/preview.php', ['cmid' => $this->contextinstanceid, 'id' => $this->objectid]); } - return new \moodle_url('/question/preview.php', ['courseid' => $this->courseid, 'id' => $this->objectid]); + return new \moodle_url('/question/bank/previewquestion/preview.php', ['courseid' => $this->courseid, 'id' => $this->objectid]); } // Lets try editing from the frontpage for contexts above course. - return new \moodle_url('/question/preview.php', ['courseid' => SITEID, 'id' => $this->objectid]); + return new \moodle_url('/question/bank/previewquestion/preview.php', ['courseid' => SITEID, 'id' => $this->objectid]); } } diff --git a/lib/classes/plugininfo/qbank.php b/lib/classes/plugininfo/qbank.php index d2e8540e446..8978dd90423 100644 --- a/lib/classes/plugininfo/qbank.php +++ b/lib/classes/plugininfo/qbank.php @@ -25,8 +25,6 @@ namespace core\plugininfo; -defined('MOODLE_INTERNAL') || die(); - /** * Base class for qbank plugins. * @@ -38,6 +36,9 @@ defined('MOODLE_INTERNAL') || die(); class qbank extends base { public function is_uninstall_allowed(): bool { + if (in_array($this->name, \core_plugin_manager::standard_plugins_list('qbank'))) { + return false; + } return true; } diff --git a/lib/db/renamedclasses.php b/lib/db/renamedclasses.php index 8e58d6d9812..f5eedc44266 100644 --- a/lib/db/renamedclasses.php +++ b/lib/db/renamedclasses.php @@ -35,17 +35,51 @@ defined('MOODLE_INTERNAL') || die(); // Like other files in the db directory this file uses an array. // The old class name is the key, the new class name is the value. // The array must be called $renamedclasses. -$renamedclasses = array( +$renamedclasses = [ // Since Moodle 3.7. 'core\\analytics\\target\\course_dropout' => 'core_course\\analytics\\target\\course_dropout', 'core\\analytics\\target\\course_competencies' => 'core_course\\analytics\\target\\course_competencies', 'core\\analytics\\target\\course_completion' => 'core_course\\analytics\\target\\course_completion', 'core\\analytics\\target\\course_gradetopass' => 'core_course\\analytics\\target\\course_gradetopass', 'core\\analytics\\target\\no_teaching' => 'core_course\\analytics\\target\\no_teaching', + // Since Moodle 4.0. 'format_base' => 'core_courseformat\\base', 'format_topics_renderer' => 'format_topics\\output\\renderer', 'format_section_renderer_base' => 'core_courseformat\\output\\section_renderer', 'format_singleactivity_renderer' => 'format_singleactivity\\output\\renderer', 'format_site_renderer' => 'core_courseformat\\output\\site_renderer', 'format_weeks_renderer' => 'format_weeks\\output\\renderer', -); + 'core_question\\bank\\action_column_base' => 'core_question\\local\\bank\\action_column_base', + 'core_question\\bank\\checkbox_column' => 'core_question\\local\\bank\\checkbox_column', + 'core_question\\bank\\column_base' => 'core_question\\local\\bank\\column_base', + 'core_question\\bank\\edit_menu_column' => 'core_question\\local\\bank\\edit_menu_column', + 'core_question\\bank\\menu_action_column_base' => 'core_question\\local\\bank\\menu_action_column_base', + 'core_question\\bank\\menuable_action' => 'core_question\\local\\bank\\menuable_action', + 'core_question\\bank\\random_question_loader' => 'core_question\\local\\bank\\random_question_loader', + 'core_question\\bank\\row_base' => 'core_question\\local\\bank\\row_base', + 'core_question\\bank\\view' => 'core_question\\local\\bank\\view', + 'core_question\\bank\\copy_action_column' => 'qbank_editquestion\\copy_action_column', + 'core_question\\bank\\edit_action_column' => 'qbank_editquestion\\edit_action_column', + 'core_question\\bank\\creator_name_column' => 'qbank_viewcreator\\creator_name_column', + 'core_question\\bank\\modifier_name_column' => 'qbank_viewcreator\\modifier_name_column', + 'core_question\\bank\\question_name_column' => 'qbank_viewquestionname\\viewquestionname_column_helper', + 'core_question\\bank\\question_name_idnumber_tags_column' => 'qbank_viewquestionname\\question_name_idnumber_tags_column', + 'core_question\\bank\\delete_action_column' => 'qbank_deletequestion\\delete_action_column', + 'core_question\\bank\\export_xml_action_column' => 'qbank_exporttoxml\\export_xml_action_column', + 'core_question\\bank\\preview_action_column' => 'qbank_previewquestion\\preview_action_column', + 'core_question\\bank\\question_text_row' => 'qbank_viewquestiontext\\question_text_row', + 'core_question\\bank\\question_type_column' => 'qbank_viewquestiontype\\question_type_column', + 'core_question\\bank\\tags_action_column' => 'qbank_tagquestion\\tags_action_column', + 'core_question\\output\\qbank_chooser' => 'qbank_editquestion\\qbank_chooser', + 'core_question\\output\\qbank_chooser_item' => 'qbank_editquestion\\qbank_chooser_item', + 'question_move_form' => 'qbank_managecategories\\form\\question_move_form', + 'question_import_form' => 'qbank_importquestions\\form\\question_import_form', + 'question_category_list' => 'qbank_managecategories\\question_category_list', + 'question_category_list_item' => 'qbank_managecategories\\question_category_list_item', + 'question_category_object' => 'qbank_managecategories\\question_category_object', + 'category_form' => 'qbank_managecategories\\form\\category_form', + 'export_form' => 'qbank_exportquestions\\form\\export_form', + 'preview_options_form' => 'qbank_previewquestion\\form\\preview_options_form', + 'question_preview_options' => 'qbank_previewquestion\\output\\question_preview_options', + 'core_question\\form\\tags' => '\qbank_tagquestion\\form\\tags_form' +]; diff --git a/lib/questionlib.php b/lib/questionlib.php index e8eaa55b603..1fdf92f5add 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -759,14 +759,12 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte * @return moodle_url the URL. * @deprecated since Moodle 4.0 * @see qbank_previewquestion\helper::question_preview_url() - * @todo MDL-71679 uncomment the debug messages after implementing the changes in mod_quiz. * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_url($questionid, $preferredbehaviour = null, $maxmark = null, $displayoptions = null, $variant = null, $context = null) { - // Debugging message will be re-added after implementing the changes in mod_quiz. - // ...debugging('Function question_preview_url() has been deprecated and moved to qbank_previewquestion plugin, - // Please use qbank_previewquestion\helper::question_preview_url() instead.', DEBUG_DEVELOPER);. + debugging('Function question_preview_url() has been deprecated and moved to qbank_previewquestion plugin, + Please use qbank_previewquestion\helper::question_preview_url() instead.', DEBUG_DEVELOPER); return \qbank_previewquestion\helper::question_preview_url($questionid, $preferredbehaviour = null, $maxmark = null, $displayoptions = null, $variant = null, $context = null); @@ -776,13 +774,11 @@ function question_preview_url($questionid, $preferredbehaviour = null, * @return array that can be passed as $params to the {@link popup_action} constructor. * @deprecated since Moodle 4.0 * @see qbank_previewquestion\helper::question_preview_popup_params() - * @todo MDL-71679 uncomment the debug messages after implementing the changes to mod_quiz. * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_popup_params() { - // Debugging message will be re-added after implementing the changes in mod_quiz. - // ...debugging('Function question_preview_popup_params() has been deprecated and moved to qbank_previewquestion plugin, - // Please use qbank_previewquestion\helper::question_preview_popup_params() instead.', DEBUG_DEVELOPER);. + debugging('Function question_preview_popup_params() has been deprecated and moved to qbank_previewquestion plugin, + Please use qbank_previewquestion\helper::question_preview_popup_params() instead.', DEBUG_DEVELOPER); return \qbank_previewquestion\helper::question_preview_popup_params(); } @@ -1656,18 +1652,9 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c 'title' => get_string('questions', 'question'), 'url' => new moodle_url($baseurl) ], - 'categories' => [ - 'title' => get_string('categories', 'question'), - 'url' => new moodle_url('/question/category.php') - ], - 'import' => [ - 'title' => get_string('import', 'question'), - 'url' => new moodle_url('/question/import.php') - ], - 'export' => [ - 'title' => get_string('export', 'question'), - 'url' => new moodle_url('/question/export.php') - ] + 'categories' => [], + 'import' => [], + 'export' => [] ]; $plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php'); @@ -1692,16 +1679,23 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c ]; } } + } + // Mitigate the risk of regression. + foreach ($corenavigations as $node => $corenavigation) { + if (empty($corenavigation)) { + unset($corenavigations[$node]); + } } // Community/additional plugins have navigation node. $pluginnavigations = []; foreach ($plugins as $componentname => $plugin) { - $pluginentrypoin = new $plugin(); - $pluginentrypointobject = $pluginentrypoin->get_navigation_node(); - if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) { - unset($corenavigations[$key]); + $pluginentrypoint = new $plugin(); + $pluginentrypointobject = $pluginentrypoint->get_navigation_node(); + // Don't need the plugins without navigation node. + if ($pluginentrypointobject === null || !\core\plugininfo\qbank::is_plugin_enabled($componentname)) { + unset($plugins[$componentname]); continue; } $pluginnavigations[$pluginentrypointobject->get_navigation_key()] = [ diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 7f89044137f..e7a75fd6832 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -69,6 +69,11 @@ information provided here is intended especially for developers. rendered. The default icon for "select" types has also changed to a dropdown caret ("t/expanded"). * The function message_send() in messagelib.php now returns false if there is an error sending the message to the message processor (MDL-70046). +* Following methods are now deprecated in questionlib and moved to the new qbank_previewquestion plugin: + - question_preview_url() is moved to qbank_previewquestion\helper::question_preview_url() + - question_preview_popup_params() is moved to \qbank_previewquestion\helper::question_preview_popup_params() + Calling these functions in the question will point to the plugin. + The deprecated codes are removed from the questionlib for those two methods. * The following functions are deprecated in questionlib.php and moved to the new location. These are marked for final deprecation on 4.4: - question_remove_stale_questions_from_category() => diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 261b4a19afc..91a60482da2 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -2107,7 +2107,7 @@ class quiz_attempt { } else { $tagids = quiz_retrieve_slot_tag_ids($this->slots[$slot]->id); - $randomloader = new \core_question\bank\random_question_loader($qubaids, array()); + $randomloader = new \core_question\local\bank\random_question_loader($qubaids, array()); $newqusetionid = $randomloader->get_next_question_id($questiondata->category, (bool) $questiondata->questiontext, $tagids); if ($newqusetionid === null) { diff --git a/mod/quiz/classes/output/edit_renderer.php b/mod/quiz/classes/output/edit_renderer.php index f7d15b75a30..6f0cdda398b 100644 --- a/mod/quiz/classes/output/edit_renderer.php +++ b/mod/quiz/classes/output/edit_renderer.php @@ -651,7 +651,7 @@ class edit_renderer extends \plugin_renderer_base { 'addonpage' => $page, 'appendqnumstring' => 'addquestion'); $actions['addaquestion'] = new \action_menu_link_secondary( - new \moodle_url('/question/addquestion.php', $params), + new \moodle_url('/question/bank/editquestion/addquestion.php', $params), new \pix_icon('t/add', $str->addaquestion, 'moodle', array('class' => 'iconsmall', 'title' => '')), $str->addaquestion, array('class' => 'cm-edit-action addquestion', 'data-action' => 'addquestion') ); @@ -722,7 +722,7 @@ class edit_renderer extends \plugin_renderer_base { return html_writer::tag('form', html_writer::div($output), array('class' => 'addnewquestion', 'method' => 'post', - 'action' => new \moodle_url('/question/addquestion.php'))); + 'action' => new \moodle_url('/question/bank/editquestion/addquestion.php'))); } /** @@ -836,7 +836,7 @@ class edit_renderer extends \plugin_renderer_base { $image = $this->pix_icon('t/preview', $strpreviewquestion); $action = new \popup_action('click', $url, 'questionpreview', - question_preview_popup_params()); + \qbank_previewquestion\helper::question_preview_popup_params()); return $this->action_link($url, $image . $strpreviewlabel, $action, array('title' => $strpreviewquestion, 'class' => 'preview')); @@ -946,7 +946,7 @@ class edit_renderer extends \plugin_renderer_base { $output = ''; $question = $structure->get_question_in_slot($slot); - $editurl = new \moodle_url('/question/question.php', array( + $editurl = new \moodle_url('/question/bank/editquestion/question.php', array( 'returnurl' => $pageurl->out_as_local_url(), 'cmid' => $structure->get_cmid(), 'id' => $question->id)); @@ -1252,9 +1252,7 @@ class edit_renderer extends \plugin_renderer_base { */ public function question_bank_contents(\mod_quiz\question\bank\custom_view $questionbank, array $pagevars) { - $qbank = $questionbank->render('editq', $pagevars['qpage'], $pagevars['qperpage'], - $pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'], $pagevars['qbshowtext'], - $pagevars['qtagids']); + $qbank = $questionbank->render($pagevars, 'editq'); return html_writer::div(html_writer::div($qbank, 'bd'), 'questionbankformforpopup'); } } diff --git a/mod/quiz/classes/output/question_chooser.php b/mod/quiz/classes/output/question_chooser.php index ee16e1d0e66..f58ffe114e2 100644 --- a/mod/quiz/classes/output/question_chooser.php +++ b/mod/quiz/classes/output/question_chooser.php @@ -32,5 +32,5 @@ defined('MOODLE_INTERNAL') || die(); * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class question_chooser extends \core_question\output\qbank_chooser { +class question_chooser extends \qbank_editquestion\qbank_chooser { } diff --git a/mod/quiz/classes/question/bank/add_action_column.php b/mod/quiz/classes/question/bank/add_action_column.php index cf314b55f9b..d47367488f9 100644 --- a/mod/quiz/classes/question/bank/add_action_column.php +++ b/mod/quiz/classes/question/bank/add_action_column.php @@ -14,30 +14,23 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * A column type for the add this question to the quiz action. - * - * @package mod_quiz - * @category question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_quiz\question\bank; -defined('MOODLE_INTERNAL') || die(); - /** * A column type for the add this question to the quiz action. * + * @package mod_quiz + * @category question * @copyright 2009 Tim Hunt + * @author 2021 Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class add_action_column extends \core_question\bank\action_column_base { +class add_action_column extends \core_question\local\bank\action_column_base { + /** @var string caches a lang string used repeatedly. */ protected $stradd; - public function init() { + public function init(): void { parent::init(); $this->stradd = get_string('addtoquiz', 'quiz'); } diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php index 0e15338176a..4f4f4036e3a 100644 --- a/mod/quiz/classes/question/bank/custom_view.php +++ b/mod/quiz/classes/question/bank/custom_view.php @@ -31,18 +31,22 @@ use coding_exception; * Subclass to customise the view of the question bank for the quiz editing screen. * * @copyright 2009 Tim Hunt + * @author 2021 Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class custom_view extends \core_question\bank\view { - /** @var bool whether the quiz this is used by has been attemptd. */ +class custom_view extends \core_question\local\bank\view { + + /** @var bool $quizhasattempts whether the quiz this is used by has been attemptd. */ protected $quizhasattempts = false; - /** @var \stdClass the quiz settings. */ + + /** @var \stdClass $quiz the quiz settings. */ protected $quiz = false; + /** @var int The maximum displayed length of the category info. */ const MAX_TEXT_LENGTH = 200; /** - * Constructor + * Constructor for custom_view. * @param \question_edit_contexts $contexts * @param \moodle_url $pageurl * @param \stdClass $course course settings @@ -54,76 +58,66 @@ class custom_view extends \core_question\bank\view { $this->quiz = $quiz; } - protected function wanted_columns() { - global $CFG; - - if (empty($CFG->quizquestionbankcolumns)) { - $quizquestionbankcolumns = array( - 'add_action_column', - 'checkbox_column', - 'question_type_column', - 'question_name_text_column', - 'preview_action_column', - ); - } else { - $quizquestionbankcolumns = explode(',', $CFG->quizquestionbankcolumns); - } + protected function wanted_columns(): array { + $quizquestionbankcolumns = [ + 'mod_quiz\\question\\bank\\add_action_column', + 'core_question\\local\\bank\\checkbox_column', + 'qbank_viewquestiontype\\question_type_column', + 'mod_quiz\\question\\bank\\question_name_text_column', + 'qbank_previewquestion\\preview_action_column', + ]; foreach ($quizquestionbankcolumns as $fullname) { if (!class_exists($fullname)) { - if (class_exists('mod_quiz\\question\\bank\\' . $fullname)) { - $fullname = 'mod_quiz\\question\\bank\\' . $fullname; - } else if (class_exists('core_question\\bank\\' . $fullname)) { - $fullname = 'core_question\\bank\\' . $fullname; - } else if (class_exists('question_bank_' . $fullname)) { - debugging('Legacy question bank column class question_bank_' . - $fullname . ' should be renamed to mod_quiz\\question\\bank\\' . - $fullname, DEBUG_DEVELOPER); - $fullname = 'question_bank_' . $fullname; - } else { - throw new coding_exception('Invalid quiz question bank column', $fullname); - } + throw new coding_exception('Invalid quiz question bank column', $fullname); } $this->requiredcolumns[$fullname] = new $fullname($this); } return $this->requiredcolumns; } - /** - * Specify the column heading - * - * @return string Column name for the heading - */ - protected function heading_column() { + protected function heading_column(): string { return 'mod_quiz\\question\\bank\\question_name_text_column'; } - protected function default_sort() { - return array( - 'core_question\\bank\\question_type_column' => 1, + protected function default_sort(): array { + return [ + 'qbank_viewquestiontype\\question_type_column' => 1, 'mod_quiz\\question\\bank\\question_name_text_column' => 1, - ); + ]; } /** * Let the question bank display know whether the quiz has been attempted, * hence whether some bits of UI, like the add this question to the quiz icon, * should be displayed. + * * @param bool $quizhasattempts whether the quiz has attempts. */ - public function set_quiz_has_attempts($quizhasattempts) { + public function set_quiz_has_attempts($quizhasattempts): void { $this->quizhasattempts = $quizhasattempts; if ($quizhasattempts && isset($this->visiblecolumns['addtoquizaction'])) { unset($this->visiblecolumns['addtoquizaction']); } } + /** + * Question preview url. + * + * @param \stdClass $question + * @return \moodle_url + */ public function preview_question_url($question) { return quiz_question_preview_url($this->quiz, $question); } + /** + * URL of add to quiz. + * + * @param $questionid + * @return \moodle_url + */ public function add_to_quiz_url($questionid) { - global $CFG; $params = $this->baseurl->params(); $params['addquestion'] = $questionid; $params['sesskey'] = sesskey(); @@ -136,34 +130,26 @@ class custom_view extends \core_question\bank\view { * Note that you can only output this rendered result once per page, as * it contains IDs which must be unique. * + * @param array $pagevars + * @param string $tabname * @return string HTML code for the form */ - public function render($tabname, $page, $perpage, $cat, $recurse, $showhidden, - $showquestiontext, $tagids = []) { + public function render($pagevars, $tabname): string { ob_start(); - $this->display($tabname, $page, $perpage, $cat, $recurse, $showhidden, $showquestiontext, $tagids); + $this->display($pagevars, $tabname); $out = ob_get_contents(); ob_end_clean(); return $out; } - /** - * Display the controls at the bottom of the list of questions. - * @param int $totalnumber Total number of questions that might be shown (if it was not for paging). - * @param bool $recurse Whether to include subcategories. - * @param \stdClass $category The question_category row from the database. - * @param \context $catcontext The context of the category being displayed. - * @param array $addcontexts contexts where the user is allowed to add new questions. - */ - protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) { + protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts): void { $cmoptions = new \stdClass(); $cmoptions->hasattempts = !empty($this->quizhasattempts); $canuseall = has_capability('moodle/question:useall', $catcontext); - echo '
'; + echo \html_writer::start_tag('div', ['class' => 'modulespecificbuttonscontainer']); if ($canuseall) { - // Add selected questions to the quiz. $params = array( 'type' => 'submit', @@ -177,75 +163,19 @@ class custom_view extends \core_question\bank\view { ); echo \html_writer::empty_tag('input', $params); } - echo "
\n"; + echo \html_writer::end_tag('div'); } - /** - * Prints a form to choose categories. - * @param string $categoryandcontext 'categoryID,contextID'. - * @deprecated since Moodle 2.6 MDL-40313. - * @see \core_question\bank\search\category_condition - * @todo MDL-41978 This will be deleted in Moodle 2.8 - */ - protected function print_choose_category_message($categoryandcontext) { - global $OUTPUT; - debugging('print_choose_category_message() is deprecated, ' . - 'please use \core_question\bank\search\category_condition instead.', DEBUG_DEVELOPER); - echo $OUTPUT->box_start('generalbox questionbank'); - $this->display_category_form($this->contexts->having_one_edit_tab_cap('edit'), - $this->baseurl, $categoryandcontext); - echo "

"; - print_string('selectcategoryabove', 'question'); - echo "

"; - echo $OUTPUT->box_end(); - } - - protected function display_options_form($showquestiontext, $scriptpath = '/mod/quiz/edit.php', - $showtextoption = false) { - // Overridden just to change the default values of the arguments. - parent::display_options_form($showquestiontext, $scriptpath, $showtextoption); - } - - protected function print_category_info($category) { - $formatoptions = new \stdClass(); - $formatoptions->noclean = true; - $strcategory = get_string('category', 'quiz'); - echo '
' . - $strcategory; - echo ': '; - echo shorten_text(strip_tags(format_string($category->name)), 60); - echo '
' . - ''; - echo shorten_text(strip_tags(format_text($category->info, $category->infoformat, - $formatoptions, $this->course->id)), 200); - echo '
'; - } - - protected function display_options($recurse, $showhidden, $showquestiontext) { - debugging('display_options() is deprecated, see display_options_form() instead.', DEBUG_DEVELOPER); - echo '
'; - echo "
"; - echo \html_writer::input_hidden_params($this->baseurl, - array('recurse', 'showhidden', 'qbshowtext')); - $this->display_category_form_checkbox('recurse', $recurse, - get_string('includesubcategories', 'question')); - $this->display_category_form_checkbox('showhidden', $showhidden, - get_string('showhidden', 'question')); - echo '
'; - } - - protected function create_new_question_form($category, $canadd) { + protected function create_new_question_form($category, $canadd): void { // Don't display this. } /** - * Override the base implementation in \core_question\bank\view + * Override the base implementation in \core_question\local\bank\view * because we don't want to print the headers in the fragment * for the modal. */ - protected function display_question_bank_header() { + protected function display_question_bank_header(): void { } /** @@ -256,7 +186,7 @@ class custom_view extends \core_question\bank\view { * Unfortunately the best we can do is to look at the URL for * those parameters (only marginally better really). */ - protected function init_sort_from_params() { + protected function init_sort_from_params(): void { $this->sort = []; for ($i = 1; $i <= self::MAX_SORTS; $i++) { if (!$sort = $this->baseurl->param('qbs' . $i)) { @@ -272,8 +202,8 @@ class custom_view extends \core_question\bank\view { } } // Deal with subsorts. - list($colname, $subsort) = $this->parse_subsort($sort); - $this->requiredcolumns[$colname] = $this->get_column_type($colname); + list($colname) = $this->parse_subsort($sort); + $this->get_column_type($colname); $this->sort[$sort] = $order; } } diff --git a/mod/quiz/classes/question/bank/question_name_column.php b/mod/quiz/classes/question/bank/question_name_column.php index 43ff13d4476..4f1462cf816 100644 --- a/mod/quiz/classes/question/bank/question_name_column.php +++ b/mod/quiz/classes/question/bank/question_name_column.php @@ -14,39 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * A column type for the name of the question name. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_quiz\question\bank; -defined('MOODLE_INTERNAL') || die(); - /** * A column type for the name of the question name. * + * @package mod_quiz + * @category question * @copyright 2009 Tim Hunt + * @author 2021 Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin */ -class question_name_column extends \core_question\bank\column_base { +class question_name_column extends \core_question\local\bank\column_base { + + /** + * @var null $checkboxespresent + */ protected $checkboxespresent = null; - public function get_name() { + public function get_name(): string { return 'questionname'; } - protected function get_title() { + protected function get_title(): string { return get_string('question'); } - protected function label_for($question) { + protected function label_for($question): string { if (is_null($this->checkboxespresent)) { - $this->checkboxespresent = $this->qbank->has_column('core_question\bank\checkbox_column'); + $this->checkboxespresent = $this->qbank->has_column('core_question\local\bank\checkbox_column'); } if ($this->checkboxespresent) { return 'checkq' . $question->id; @@ -55,19 +51,19 @@ class question_name_column extends \core_question\bank\column_base { } } - protected function display_content($question, $rowclasses) { + protected function display_content($question, $rowclasses): void { $labelfor = $this->label_for($question); if ($labelfor) { - echo ''; + echo \html_writer::end_tag('label'); } } - public function get_required_fields() { - return array('q.id', 'q.name'); + public function get_required_fields(): array { + return ['q.id', 'q.name']; } public function is_sortable() { diff --git a/mod/quiz/classes/question/bank/question_name_text_column.php b/mod/quiz/classes/question/bank/question_name_text_column.php index 65e8437e62f..2439d315f49 100644 --- a/mod/quiz/classes/question/bank/question_name_text_column.php +++ b/mod/quiz/classes/question/bank/question_name_text_column.php @@ -14,44 +14,37 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * A column type for the name followed by the start of the question text. - * - * @package mod_quiz - * @category question - * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_quiz\question\bank; -defined('MOODLE_INTERNAL') || die(); - /** * A column type for the name followed by the start of the question text. * + * @package mod_quiz + * @category question * @copyright 2009 Tim Hunt + * @author 2021 Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class question_name_text_column extends question_name_column { - public function get_name() { + + public function get_name(): string { return 'questionnametext'; } - protected function display_content($question, $rowclasses) { - echo '
'; + protected function display_content($question, $rowclasses): void { + echo \html_writer::start_tag('div'); $labelfor = $this->label_for($question); if ($labelfor) { - echo ''; + echo \html_writer::end_tag('label'); } - echo '
'; + echo \html_writer::end_tag('div'); } - public function get_required_fields() { + public function get_required_fields(): array { $fields = parent::get_required_fields(); $fields[] = 'q.questiontext'; $fields[] = 'q.questiontextformat'; diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index e72c71d58e2..4950902156e 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -171,7 +171,7 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) { // Get the question bank view. $questionbank = new mod_quiz\question\bank\custom_view($contexts, $thispageurl, $course, $cm, $quiz); $questionbank->set_quiz_has_attempts($quizhasattempts); -$questionbank->process_actions($thispageurl, $cm); +$questionbank->process_actions(); // End of process commands =====================================================. diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 34bc48b1df9..9bbc6862d15 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -199,7 +199,7 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time $usedquestionids[$question->id] = 1; } } - $randomloader = new \core_question\bank\random_question_loader($qubaids, $usedquestionids); + $randomloader = new \core_question\local\bank\random_question_loader($qubaids, $usedquestionids); foreach ($quizobj->get_questions() as $questiondata) { $slot += 1; @@ -1401,7 +1401,7 @@ function quiz_question_edit_button($cmid, $question, $returnurl, $contentafteric $returnurl = $returnurl->out_as_local_url(false); } $questionparams = array('returnurl' => $returnurl, 'cmid' => $cmid, 'id' => $question->id); - $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); + $questionurl = new moodle_url("$CFG->wwwroot/question/bank/editquestion/question.php", $questionparams); return '' . $OUTPUT->pix_icon($icon, $action) . $contentaftericon . ''; @@ -1429,7 +1429,7 @@ function quiz_question_preview_url($quiz, $question, $variant = null) { } // Work out the correcte preview URL. - return question_preview_url($question->id, $quiz->preferredbehaviour, + return \qbank_previewquestion\helper::question_preview_url($question->id, $quiz->preferredbehaviour, $maxmark, $displayoptions, $variant); } diff --git a/mod/quiz/tests/behat/behat_mod_quiz.php b/mod/quiz/tests/behat/behat_mod_quiz.php index add61fdcda8..dcffafdcd32 100644 --- a/mod/quiz/tests/behat/behat_mod_quiz.php +++ b/mod/quiz/tests/behat/behat_mod_quiz.php @@ -429,8 +429,7 @@ class behat_mod_quiz extends behat_question_base { */ public function i_should_see_on_quiz_page($questionname, $pagenumber) { $xpath = "//li[contains(., '" . $this->escape($questionname) . - "')][./preceding-sibling::li[contains(@class, 'pagenumber')][1][contains(., 'Page " . - $pagenumber . "')]]"; + "')][./preceding-sibling::li[contains(., 'Page " . $pagenumber . "')]]"; $this->execute('behat_general::should_exist', array($xpath, 'xpath_element')); } @@ -457,8 +456,8 @@ class behat_mod_quiz extends behat_question_base { * @param string $secondquestionname the name of the question that should come immediately after it in order. */ public function i_should_see_before_on_the_edit_quiz_page($firstquestionname, $secondquestionname) { - $xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($firstquestionname) . - "')]/following-sibling::li[contains(@class, ' slot ')][1]" . + $xpath = "//li[contains(., '" . $this->escape($firstquestionname) . + "')]/following-sibling::li" . "[contains(., '" . $this->escape($secondquestionname) . "')]"; $this->execute('behat_general::should_exist', array($xpath, 'xpath_element')); diff --git a/mod/quiz/tests/behat/editing_add_from_question_bank.feature b/mod/quiz/tests/behat/editing_add_from_question_bank.feature index 68cf20ef9ef..68e2b043670 100644 --- a/mod/quiz/tests/behat/editing_add_from_question_bank.feature +++ b/mod/quiz/tests/behat/editing_add_from_question_bank.feature @@ -79,7 +79,7 @@ Feature: Adding questions to a quiz from the question bank Then I should see "question 21 name" in the "categoryquestions" "table" And I should see "question 22 name" in the "categoryquestions" "table" And I should not see "question 01 name" in the "categoryquestions" "table" - And I click on "Show all 22" "link" in the ".pagingbottom" "css_element" + And I click on "Show all 22" "link" in the ".question-showall-text" "css_element" And I should see "question 01 name" in the "categoryquestions" "table" And I should see "question 22 name" in the "categoryquestions" "table" diff --git a/mod/quiz/tests/quiz_question_bank_view_test.php b/mod/quiz/tests/quiz_question_bank_view_test.php index 2f99a2b537f..39115168315 100644 --- a/mod/quiz/tests/quiz_question_bank_view_test.php +++ b/mod/quiz/tests/quiz_question_bank_view_test.php @@ -63,7 +63,15 @@ class quiz_question_bank_view_testcase extends advanced_testcase { // Generate the view. $view = new mod_quiz\question\bank\custom_view($contexts, new moodle_url('/'), $course, $cm, $quiz); ob_start(); - $view->display('editq', 0, 20, $cat->id . ',' . $cat->contextid, false, false, false); + $pagevars = [ + 'qpage' => 0, + 'qperpage' => 20, + 'cat' => $cat->id . ',' . $context->id, + 'recurse' => false, + 'showhidden' => false, + 'qbshowtext' => false + ]; + $view->display($pagevars, 'editq'); $html = ob_get_clean(); // Verify the output includes the expected question. diff --git a/question/addquestion.php b/question/addquestion.php deleted file mode 100644 index c7ca9823876..00000000000 --- a/question/addquestion.php +++ /dev/null @@ -1,93 +0,0 @@ -. - -/** - * Shows a screen where the user can choose a question type, before being - * redirected to question.php - * - * @package moodlecore - * @subpackage questionbank - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(__DIR__ . '/../config.php'); -require_once(__DIR__ . '/editlib.php'); - -// Read URL parameters. -$categoryid = required_param('category', PARAM_INT); -$cmid = optional_param('cmid', 0, PARAM_INT); -$courseid = optional_param('courseid', 0, PARAM_INT); -$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL); -$appendqnumstring = optional_param('appendqnumstring', '', PARAM_ALPHA); -$validationerror = optional_param('validationerror', false, PARAM_BOOL); - -// Place to accumulate hidden params for the form we will print. -$hiddenparams = array('category' => $categoryid); - -// Validate params. -if (!$category = $DB->get_record('question_categories', array('id' => $categoryid))) { - print_error('categorydoesnotexist', 'question', $returnurl); -} - -if ($cmid) { - list($module, $cm) = get_module_from_cmid($cmid); - require_login($cm->course, false, $cm); - $thiscontext = context_module::instance($cmid); - $hiddenparams['cmid'] = $cmid; -} else if ($courseid) { - require_login($courseid, false); - $thiscontext = context_course::instance($courseid); - $module = null; - $cm = null; - $hiddenparams['courseid'] = $courseid; -} else { - print_error('missingcourseorcmid', 'question'); -} - -// Check permissions. -$categorycontext = context::instance_by_id($category->contextid); -require_capability('moodle/question:add', $categorycontext); - -// Ensure other optional params get passed on to question.php. -if (!empty($returnurl)) { - $hiddenparams['returnurl'] = $returnurl; -} -if (!empty($appendqnumstring)) { - $hiddenparams['appendqnumstring'] = $appendqnumstring; -} - -$PAGE->set_url('/question/addquestion.php', $hiddenparams); -if ($cmid) { - $questionbankurl = new moodle_url('/question/edit.php', array('cmid' => $cmid)); -} else { - $questionbankurl = new moodle_url('/question/edit.php', array('courseid' => $courseid)); -} -navigation_node::override_active_url($questionbankurl); - -$chooseqtype = get_string('chooseqtypetoadd', 'question'); -$PAGE->set_heading($COURSE->fullname); -$PAGE->navbar->add($chooseqtype); -$PAGE->set_title($chooseqtype); - -// Display a form to choose the question type. -echo $OUTPUT->header(); -echo $OUTPUT->notification(get_string('youmustselectaqtype', 'question')); -echo $OUTPUT->box_start('generalbox boxwidthnormal boxaligncenter', 'chooseqtypebox'); -echo print_choose_qtype_to_add_form($hiddenparams, null, false); -echo $OUTPUT->box_end(); -echo $OUTPUT->footer(); diff --git a/question/category_class.php b/question/category_class.php index e4e576b34a5..9084fb6b934 100644 --- a/question/category_class.php +++ b/question/category_class.php @@ -21,608 +21,9 @@ * @subpackage questionbank * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -// number of categories to display on page -define('QUESTION_PAGE_LENGTH', 25); - -require_once($CFG->libdir . '/listlib.php'); -require_once($CFG->dirroot . '/question/category_form.php'); -require_once($CFG->dirroot . '/question/move_form.php'); - - -/** - * Class representing a list of question categories - * - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.0 MDL-71585 * @see \qbank_managecategories\question_category_list - * @todo deprecation on MDL-71679 - */ -class question_category_list extends moodle_list { - public $table = "question_categories"; - public $listitemclassname = 'question_category_list_item'; - /** - * @var reference to list displayed below this one. - */ - public $nextlist = null; - /** - * @var reference to list displayed above this one. - */ - public $lastlist = null; - - public $context = null; - public $sortby = 'parent, sortorder, name'; - - public function __construct($type='ul', $attributes='', $editable = false, $pageurl=null, $page = 0, $pageparamname = 'page', $itemsperpage = 20, $context = null){ - debugging('Class question_category_list in \core_question\category_class is deprecated, - please use qbank_managecategories\question_category_list instead.', DEBUG_DEVELOPER); - - parent::__construct('ul', '', $editable, $pageurl, $page, 'cpage', $itemsperpage); - $this->context = $context; - } - - public function get_records() { - $this->records = get_categories_for_contexts($this->context->id, $this->sortby); - } - - /** - * Returns the highest category id that the $item can have as its parent. - * Note: question categories cannot go higher than the TOP category. - * - * @param list_item $item The item which its top level parent is going to be returned. - * @return int - */ - public function get_top_level_parent_id($item) { - // Put the item at the highest level it can go. - $topcategory = question_get_top_category($item->item->contextid, true); - return $topcategory->id; - } - - /** - * process any actions. - * - * @param integer $left id of item to move left - * @param integer $right id of item to move right - * @param integer $moveup id of item to move up - * @param integer $movedown id of item to move down - * @return void - * @throws coding_exception - */ - public function process_actions($left, $right, $moveup, $movedown) { - $category = new stdClass(); - if (!empty($left)) { - // Moved Left (In to another category). - $category->id = $left; - $category->contextid = $this->context->id; - $event = \core\event\question_category_moved::create_from_question_category_instance($category); - $event->trigger(); - } else if (!empty($right)) { - // Moved Right (Out of the current category). - $category->id = $right; - $category->contextid = $this->context->id; - $event = \core\event\question_category_moved::create_from_question_category_instance($category); - $event->trigger(); - } - parent::process_actions($left, $right, $moveup, $movedown); - } -} - -/** - * An item in a list of question categories. - * - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 MDL-71585 * @see \qbank_managecategories\question_category_list_item - * @todo deprecation on MDL-71679 - */ -class question_category_list_item extends list_item { - public function set_icon_html($first, $last, $lastitem){ - debugging('Function set_icon_html() in \core_question\category_class is deprecated, - please use qbank_managecategories\question_category_list_item::set_icon_html() instead.', DEBUG_DEVELOPER); - global $CFG; - $category = $this->item; - $url = new moodle_url('/question/category.php', ($this->parentlist->pageurl->params() + array('edit'=>$category->id))); - $this->icons['edit']= $this->image_icon(get_string('editthiscategory', 'question'), $url, 'edit'); - parent::set_icon_html($first, $last, $lastitem); - $toplevel = ($this->parentlist->parentitem === null);//this is a top level item - if (($this->parentlist->nextlist !== null) && $last && $toplevel && (count($this->parentlist->items)>1)){ - $url = new moodle_url($this->parentlist->pageurl, array('movedowncontext'=>$this->id, 'tocontext'=>$this->parentlist->nextlist->context->id, 'sesskey'=>sesskey())); - $this->icons['down'] = $this->image_icon( - get_string('shareincontext', 'question', $this->parentlist->nextlist->context->get_context_name()), $url, 'down'); - } - if (($this->parentlist->lastlist !== null) && $first && $toplevel && (count($this->parentlist->items)>1)){ - $url = new moodle_url($this->parentlist->pageurl, array('moveupcontext'=>$this->id, 'tocontext'=>$this->parentlist->lastlist->context->id, 'sesskey'=>sesskey())); - $this->icons['up'] = $this->image_icon( - get_string('shareincontext', 'question', $this->parentlist->lastlist->context->get_context_name()), $url, 'up'); - } - } - - public function item_html($extraargs = array()){ - global $CFG, $PAGE, $OUTPUT; - debugging('Function item_html() in \core_question\category_class is deprecated, - please use qbank_managecategories\question_category_list_item::item_html() instead.', DEBUG_DEVELOPER); - $str = $extraargs['str']; - $category = $this->item; - - $editqestions = get_string('editquestions', 'question'); - - // Each section adds html to be displayed as part of this list item. - $nodeparent = $PAGE->settingsnav->find('questionbank', \navigation_node::TYPE_CONTAINER); - $questionbankurl = new moodle_url($nodeparent->action, $this->parentlist->pageurl->params()); - $questionbankurl->param('cat', $category->id . ',' . $category->contextid); - $item = ''; - $text = format_string($category->name, true, ['context' => $this->parentlist->context]); - if ($category->idnumber !== null && $category->idnumber !== '') { - $text .= ' ' . html_writer::span( - html_writer::span(get_string('idnumber', 'question'), 'accesshide') . - ' ' . $category->idnumber, 'badge badge-primary'); - } - $text .= ' (' . $category->questioncount . ')'; - $item .= html_writer::tag('b', html_writer::link($questionbankurl, $text, - ['title' => $editqestions]) . ' '); - $item .= format_text($category->info, $category->infoformat, - array('context' => $this->parentlist->context, 'noclean' => true)); - - // Don't allow delete if this is the top category, or the last editable category in this context. - if ($category->parent && !question_is_only_child_of_top_category_in_context($category->id)) { - $deleteurl = new moodle_url($this->parentlist->pageurl, array('delete' => $this->id, 'sesskey' => sesskey())); - $item .= html_writer::link($deleteurl, - $OUTPUT->pix_icon('t/delete', $str->delete), - array('title' => $str->delete)); - } - - return $item; - } -} - - -/** - * Class for performing operations on question categories. - * - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 MDL-71585 * @see \qbank_managecategories\question_category_object - * @todo deprecation on MDL-71679 - */ -class question_category_object { - - /** - * @var array common language strings. - */ - public $str; - - /** - * @var array nested lists to display categories. - */ - public $editlists = array(); - public $tab; - public $tabsize = 3; - - /** - * @var moodle_url Object representing url for this page - */ - public $pageurl; - - /** - * @var question_category_edit_form Object representing form for adding / editing categories. - */ - public $catform; - - /** - * Constructor. - * - * @param int $page page number - * @param moodle_url $pageurl base URL of the display categories page. Used for redirects. - * @param context[] $contexts contexts where the current user can edit categories. - * @param int $currentcat id of the category to be edited. 0 if none. - * @param int|null $defaultcategory id of the current category. null if none. - * @param int $todelete id of the category to delete. 0 if none. - * @param context[] $addcontexts contexts where the current user can add questions. - */ - public function __construct($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) { - debugging('Class question_category_list in \core_question\category_class is deprecated, - please use qbank_managecategories\question_category_object instead.', DEBUG_DEVELOPER); - - $this->tab = str_repeat(' ', $this->tabsize); - - $this->str = new stdClass(); - $this->str->course = get_string('course'); - $this->str->category = get_string('category', 'question'); - $this->str->categoryinfo = get_string('categoryinfo', 'question'); - $this->str->questions = get_string('questions', 'question'); - $this->str->add = get_string('add'); - $this->str->delete = get_string('delete'); - $this->str->moveup = get_string('moveup'); - $this->str->movedown = get_string('movedown'); - $this->str->edit = get_string('editthiscategory', 'question'); - $this->str->hide = get_string('hide'); - $this->str->order = get_string('order'); - $this->str->parent = get_string('parent', 'question'); - $this->str->add = get_string('add'); - $this->str->action = get_string('action'); - $this->str->top = get_string('top'); - $this->str->addcategory = get_string('addcategory', 'question'); - $this->str->editcategory = get_string('editcategory', 'question'); - $this->str->cancel = get_string('cancel'); - $this->str->editcategories = get_string('editcategories', 'question'); - $this->str->page = get_string('page'); - - $this->pageurl = $pageurl; - - $this->initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts); - } - - /** - * Old syntax of class constructor. Deprecated in PHP7. - * - * @deprecated since Moodle 3.1 - */ - public function question_category_object($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) { - debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); - self::__construct($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts); - } - - /** - * Initializes this classes general category-related variables - */ - public function initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) { - $lastlist = null; - foreach ($contexts as $context){ - $this->editlists[$context->id] = new question_category_list('ul', '', true, $this->pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH, $context); - $this->editlists[$context->id]->lastlist =& $lastlist; - if ($lastlist!== null){ - $lastlist->nextlist =& $this->editlists[$context->id]; - } - $lastlist =& $this->editlists[$context->id]; - } - - $count = 1; - $paged = false; - foreach ($this->editlists as $key => $list){ - list($paged, $count) = $this->editlists[$key]->list_from_records($paged, $count); - } - $this->catform = new question_category_edit_form($this->pageurl, compact('contexts', 'currentcat')); - if (!$currentcat){ - $this->catform->set_data(array('parent'=>$defaultcategory)); - } - } - - /** - * Displays the user interface - * - */ - public function display_user_interface() { - - /// Interface for editing existing categories - $this->output_edit_lists(); - - - echo '
'; - /// Interface for adding a new category: - $this->output_new_table(); - echo '
'; - - } - - /** - * Outputs a table to allow entry of a new category - */ - public function output_new_table() { - $this->catform->display(); - } - - /** - * Outputs a list to allow editing/rearranging of existing categories - * - * $this->initialize() must have already been called - * - */ - public function output_edit_lists() { - global $OUTPUT; - - echo $OUTPUT->heading_with_help(get_string('editcategories', 'question'), 'editcategories', 'question'); - - foreach ($this->editlists as $context => $list){ - $listhtml = $list->to_html(0, array('str'=>$this->str)); - if ($listhtml){ - echo $OUTPUT->box_start('boxwidthwide boxaligncenter generalbox questioncategories contextlevel' . $list->context->contextlevel); - $fullcontext = context::instance_by_id($context); - echo $OUTPUT->heading(get_string('questioncatsfor', 'question', $fullcontext->get_context_name()), 3); - echo $listhtml; - echo $OUTPUT->box_end(); - } - } - echo $list->display_page_numbers(); - } - - /** - * gets all the courseids for the given categories - * - * @param array categories contains category objects in a tree representation - * @return array courseids flat array in form categoryid=>courseid - */ - public function get_course_ids($categories) { - $courseids = array(); - foreach ($categories as $key=>$cat) { - $courseids[$key] = $cat->course; - if (!empty($cat->children)) { - $courseids = array_merge($courseids, $this->get_course_ids($cat->children)); - } - } - return $courseids; - } - - public function edit_single_category($categoryid) { - /// Interface for adding a new category - global $DB; - /// Interface for editing existing categories - $category = $DB->get_record("question_categories", array("id" => $categoryid)); - if (empty($category)) { - print_error('invalidcategory', '', '', $categoryid); - } else if ($category->parent == 0) { - print_error('cannotedittopcat', 'question', '', $categoryid); - } else { - $category->parent = "{$category->parent},{$category->contextid}"; - $category->submitbutton = get_string('savechanges'); - $category->categoryheader = $this->str->edit; - $this->catform->set_data($category); - $this->catform->display(); - } - } - - /** - * Sets the viable parents - * - * Viable parents are any except for the category itself, or any of it's descendants - * The parentstrings parameter is passed by reference and changed by this function. - * - * @param array parentstrings a list of parentstrings - * @param object category - */ - public function set_viable_parents(&$parentstrings, $category) { - - unset($parentstrings[$category->id]); - if (isset($category->children)) { - foreach ($category->children as $child) { - $this->set_viable_parents($parentstrings, $child); - } - } - } - - /** - * Gets question categories - * - * @param int parent - if given, restrict records to those with this parent id. - * @param string sort - [[sortfield [,sortfield]] {ASC|DESC}] - * @return array categories - */ - public function get_question_categories($parent=null, $sort="sortorder ASC") { - global $COURSE, $DB; - if (is_null($parent)) { - $categories = $DB->get_records('question_categories', array('course' => $COURSE->id), $sort); - } else { - $select = "parent = ? AND course = ?"; - $categories = $DB->get_records_select('question_categories', $select, array($parent, $COURSE->id), $sort); - } - return $categories; - } - - /** - * Deletes an existing question category - * - * @param int deletecat id of category to delete - */ - public function delete_category($categoryid) { - global $CFG, $DB; - question_can_delete_cat($categoryid); - if (!$category = $DB->get_record("question_categories", array("id" => $categoryid))) { // security - print_error('unknowcategory'); - } - /// Send the children categories to live with their grandparent - $DB->set_field("question_categories", "parent", $category->parent, array("parent" => $category->id)); - - /// Finally delete the category itself - $DB->delete_records("question_categories", array("id" => $category->id)); - - // Log the deletion of this category. - $event = \core\event\question_category_deleted::create_from_question_category_instance($category); - $event->add_record_snapshot('question_categories', $category); - $event->trigger(); - - } - - public function move_questions_and_delete_category($oldcat, $newcat){ - question_can_delete_cat($oldcat); - $this->move_questions($oldcat, $newcat); - $this->delete_category($oldcat); - } - - public function display_move_form($questionsincategory, $category){ - global $OUTPUT; - $vars = new stdClass(); - $vars->name = $category->name; - $vars->count = $questionsincategory; - echo $OUTPUT->box(get_string('categorymove', 'question', $vars), 'generalbox boxaligncenter'); - $this->moveform->display(); - } - - public function move_questions($oldcat, $newcat){ - global $DB; - $questionids = $DB->get_records_select_menu('question', - 'category = ? AND (parent = 0 OR parent = id)', array($oldcat), '', 'id,1'); - question_move_questions_to_category(array_keys($questionids), $newcat); - } - - /** - * Create a new category. - * - * Data is expected to come from question_category_edit_form. - * - * By default redirects on success, unless $return is true. - * - * @param string $newparent 'categoryid,contextid' of the parent category. - * @param string $newcategory the name. - * @param string $newinfo the description. - * @param bool $return if true, return rather than redirecting. - * @param int|string $newinfoformat description format. One of the FORMAT_ constants. - * @param null $idnumber the idnumber. '' is converted to null. - * @return bool|int New category id if successful, else false. - */ - public function add_category($newparent, $newcategory, $newinfo, $return = false, $newinfoformat = FORMAT_HTML, - $idnumber = null) { - global $DB; - if (empty($newcategory)) { - print_error('categorynamecantbeblank', 'question'); - } - list($parentid, $contextid) = explode(',', $newparent); - //moodle_form makes sure select element output is legal no need for further cleaning - require_capability('moodle/question:managecategory', context::instance_by_id($contextid)); - - if ($parentid) { - if(!($DB->get_field('question_categories', 'contextid', array('id' => $parentid)) == $contextid)) { - print_error('cannotinsertquestioncatecontext', 'question', '', array('cat'=>$newcategory, 'ctx'=>$contextid)); - } - } - - if ((string) $idnumber === '') { - $idnumber = null; - } else if (!empty($contextid)) { - // While this check already exists in the form validation, this is a backstop preventing unnecessary errors. - if ($DB->record_exists('question_categories', - ['idnumber' => $idnumber, 'contextid' => $contextid])) { - $idnumber = null; - } - } - - $cat = new stdClass(); - $cat->parent = $parentid; - $cat->contextid = $contextid; - $cat->name = $newcategory; - $cat->info = $newinfo; - $cat->infoformat = $newinfoformat; - $cat->sortorder = 999; - $cat->stamp = make_unique_id_code(); - $cat->idnumber = $idnumber; - $categoryid = $DB->insert_record("question_categories", $cat); - - // Log the creation of this category. - $category = new stdClass(); - $category->id = $categoryid; - $category->contextid = $contextid; - $event = \core\event\question_category_created::create_from_question_category_instance($category); - $event->trigger(); - - if ($return) { - return $categoryid; - } else { - redirect($this->pageurl);//always redirect after successful action - } - } - - /** - * Updates an existing category with given params. - * - * Warning! parameter order and meaning confusingly different from add_category in some ways! - * - * @param int $updateid id of the category to update. - * @param int $newparent 'categoryid,contextid' of the parent category to set. - * @param string $newname category name. - * @param string $newinfo category description. - * @param int|string $newinfoformat description format. One of the FORMAT_ constants. - * @param int $idnumber the idnumber. '' is converted to null. - * @param bool $redirect if true, will redirect once the DB is updated (default). - */ - public function update_category($updateid, $newparent, $newname, $newinfo, $newinfoformat = FORMAT_HTML, - $idnumber = null, $redirect = true) { - global $CFG, $DB; - if (empty($newname)) { - print_error('categorynamecantbeblank', 'question'); - } - - // Get the record we are updating. - $oldcat = $DB->get_record('question_categories', array('id' => $updateid)); - $lastcategoryinthiscontext = question_is_only_child_of_top_category_in_context($updateid); - - if (!empty($newparent) && !$lastcategoryinthiscontext) { - list($parentid, $tocontextid) = explode(',', $newparent); - } else { - $parentid = $oldcat->parent; - $tocontextid = $oldcat->contextid; - } - - // Check permissions. - $fromcontext = context::instance_by_id($oldcat->contextid); - require_capability('moodle/question:managecategory', $fromcontext); - - // If moving to another context, check permissions some more, and confirm contextid,stamp uniqueness. - $newstamprequired = false; - if ($oldcat->contextid != $tocontextid) { - $tocontext = context::instance_by_id($tocontextid); - require_capability('moodle/question:managecategory', $tocontext); - - // Confirm stamp uniqueness in the new context. If the stamp already exists, generate a new one. - if ($DB->record_exists('question_categories', array('contextid' => $tocontextid, 'stamp' => $oldcat->stamp))) { - $newstamprequired = true; - } - } - - if ((string) $idnumber === '') { - $idnumber = null; - } else if (!empty($tocontextid)) { - // While this check already exists in the form validation, this is a backstop preventing unnecessary errors. - if ($DB->record_exists_select('question_categories', - 'idnumber = ? AND contextid = ? AND id <> ?', - [$idnumber, $tocontextid, $updateid])) { - $idnumber = null; - } - } - - // Update the category record. - $cat = new stdClass(); - $cat->id = $updateid; - $cat->name = $newname; - $cat->info = $newinfo; - $cat->infoformat = $newinfoformat; - $cat->parent = $parentid; - $cat->contextid = $tocontextid; - $cat->idnumber = $idnumber; - if ($newstamprequired) { - $cat->stamp = make_unique_id_code(); - } - $DB->update_record('question_categories', $cat); - - // Log the update of this category. - $event = \core\event\question_category_updated::create_from_question_category_instance($cat); - $event->trigger(); - - // If the category name has changed, rename any random questions in that category. - if ($oldcat->name != $cat->name) { - $where = "qtype = 'random' AND category = ? AND " . $DB->sql_compare_text('questiontext') . " = ?"; - - $randomqtype = question_bank::get_qtype('random'); - $randomqname = $randomqtype->question_name($cat, false); - $DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '0')); - - $randomqname = $randomqtype->question_name($cat, true); - $DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '1')); - } - - if ($oldcat->contextid != $tocontextid) { - // Moving to a new context. Must move files belonging to questions. - question_move_category_to_context($cat->id, $oldcat->contextid, $tocontextid); - } - - // Cat param depends on the context id, so update it. - $this->pageurl->param('cat', $updateid . ',' . $tocontextid); - if ($redirect) { - redirect($this->pageurl); // Always redirect after successful action. - } - } -} + * @todo Final deprecation on Moodle 4.4 MDL-72438 + */ \ No newline at end of file diff --git a/question/category_form.php b/question/category_form.php index 6832bbda03b..79c471c93c6 100644 --- a/question/category_form.php +++ b/question/category_form.php @@ -21,102 +21,7 @@ * @subpackage questionbank * @copyright 2007 Jamie Pratt me@jamiep.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir.'/formslib.php'); - - -/** - * Form for editing qusetions categories (name, description, etc.) - * - * @copyright 2007 Jamie Pratt me@jamiep.org - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.0 MDL-71585 * @see qbank_managecategories\form\category_form + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ -class question_category_edit_form extends moodleform { - - protected function definition() { - debugging('Class question_export_form in \core_question\category_form is deprecated, - please use core_question\bank\managecategories\form\category_form instead.', DEBUG_DEVELOPER); - - $mform = $this->_form; - - $contexts = $this->_customdata['contexts']; - $currentcat = $this->_customdata['currentcat']; - - $mform->addElement('header', 'categoryheader', get_string('addcategory', 'question')); - - $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'), - array('contexts' => $contexts, 'top' => true, 'currentcat' => $currentcat, 'nochildrenof' => $currentcat)); - $mform->setType('parent', PARAM_SEQUENCE); - if (question_is_only_child_of_top_category_in_context($currentcat)) { - $mform->hardFreeze('parent'); - } - $mform->addHelpButton('parent', 'parentcategory', 'question'); - - $mform->addElement('text', 'name', get_string('name'),'maxlength="254" size="50"'); - $mform->setDefault('name', ''); - $mform->addRule('name', get_string('categorynamecantbeblank', 'question'), 'required', null, 'client'); - $mform->setType('name', PARAM_TEXT); - - $mform->addElement('editor', 'info', get_string('categoryinfo', 'question'), - array('rows' => 10), array('noclean' => 1)); - $mform->setDefault('info', ''); - $mform->setType('info', PARAM_RAW); - - $mform->addElement('text', 'idnumber', get_string('idnumber', 'question'), 'maxlength="100" size="10"'); - $mform->addHelpButton('idnumber', 'idnumber', 'question'); - $mform->setType('idnumber', PARAM_RAW); - - $this->add_action_buttons(false, get_string('addcategory', 'question')); - - $mform->addElement('hidden', 'id', 0); - $mform->setType('id', PARAM_INT); - } - - public function set_data($current) { - if (is_object($current)) { - $current = (array) $current; - } - if (!empty($current['info'])) { - $current['info'] = array('text' => $current['info'], - 'infoformat' => $current['infoformat']); - } else { - $current['info'] = array('text' => '', 'infoformat' => FORMAT_HTML); - } - parent::set_data($current); - } - - /** - * Validation. - * - * @param array $data - * @param array $files - * @return array the errors that were found - */ - public function validation($data, $files) { - global $DB; - - $errors = parent::validation($data, $files); - - // Add field validation check for duplicate idnumber. - list($parentid, $contextid) = explode(',', $data['parent']); - if (((string) $data['idnumber'] !== '') && !empty($contextid)) { - $conditions = 'contextid = ? AND idnumber = ?'; - $params = [$contextid, $data['idnumber']]; - if (!empty($data['id'])) { - $conditions .= ' AND id <> ?'; - $params[] = $data['id']; - } - if ($DB->record_exists_select('question_categories', $conditions, $params)) { - $errors['idnumber'] = get_string('idnumbertaken', 'error'); - } - } - - return $errors; - } -} diff --git a/question/classes/bank/action_column_base.php b/question/classes/bank/action_column_base.php deleted file mode 100644 index b5043c874c7..00000000000 --- a/question/classes/bank/action_column_base.php +++ /dev/null @@ -1,61 +0,0 @@ -. - -/** - * A base class for actions that are an icon that lets you manipulate the question in some way. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A base class for actions that are an icon that lets you manipulate the question in some way. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -abstract class action_column_base extends column_base { - - protected function get_title() { - return ' '; - } - - public function get_extra_classes() { - return array('iconcol'); - } - - protected function print_icon($icon, $title, $url) { - global $OUTPUT; - echo '' . $OUTPUT->pix_icon($icon, $title) . ''; - } - - public function get_extra_joins() { - return array('qc' => 'JOIN {question_categories} qc ON qc.id = q.category'); - } - - public function get_required_fields() { - // Createdby is required for permission checks. - // Qtype so we can easily avoid applying actions to question types that - // are no longer installed. - return ['q.id', 'q.qtype', 'q.createdby', 'qc.contextid']; - } -} diff --git a/question/classes/bank/checkbox_column.php b/question/classes/bank/checkbox_column.php deleted file mode 100644 index 18e59510055..00000000000 --- a/question/classes/bank/checkbox_column.php +++ /dev/null @@ -1,78 +0,0 @@ -. - -/** - * A column with a checkbox for each question with name q{questionid}. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - -use core\output\checkbox_toggleall; - - -/** - * A column with a checkbox for each question with name q{questionid}. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -class checkbox_column extends column_base { - - public function get_name() { - return 'checkbox'; - } - - protected function get_title() { - global $OUTPUT; - - $mastercheckbox = new checkbox_toggleall('qbank', true, [ - 'id' => 'qbheadercheckbox', - 'name' => 'qbheadercheckbox', - 'value' => '1', - 'label' => get_string('selectall'), - 'labelclasses' => 'accesshide', - ]); - - return $OUTPUT->render($mastercheckbox); - } - - protected function get_title_tip() { - return get_string('selectquestionsforbulk', 'question'); - } - - protected function display_content($question, $rowclasses) { - global $OUTPUT; - - $checkbox = new checkbox_toggleall('qbank', false, [ - 'id' => "checkq{$question->id}", - 'name' => "q{$question->id}", - 'value' => '1', - 'label' => get_string('select'), - 'labelclasses' => 'accesshide', - ]); - - echo $OUTPUT->render($checkbox); - } - - public function get_required_fields() { - return array('q.id'); - } -} diff --git a/question/classes/bank/column_base.php b/question/classes/bank/column_base.php deleted file mode 100644 index 3a7fe62f1ac..00000000000 --- a/question/classes/bank/column_base.php +++ /dev/null @@ -1,369 +0,0 @@ -. - -/** - * Base class for representing a column in a {@see question_bank_view}. - * - * @package core_question - * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Base class for representing a column in a {@see question_bank_view}. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -abstract class column_base { - - /** - * @var view $qbank the question bank view we are helping to render. - */ - protected $qbank; - - /** @var bool determine whether the column is td or th. */ - protected $isheading = false; - - /** - * Constructor for the question bank view we are helping to render. - * @param view $qbank - */ - public function __construct($qbank) { - $this->qbank = $qbank; - $this->init(); - } - - /** - * A chance for subclasses to initialise themselves, for example to load lang strings, - * without having to override the constructor. - */ - protected function init() { - } - - /** - * Set the column as heading - */ - public function set_as_heading() { - $this->isheading = true; - } - - public function is_extra_row() { - return false; - } - - /** - * Check if the row has an extra preference to view/hide. - */ - public function has_preference(): bool { - return false; - } - - /** - * Get if the preference key of the row. - */ - public function get_preference_key(): string { - return ''; - } - - /** - * Get if the preference of the row. - */ - public function get_preference(): bool { - return false; - } - - /** - * Output the column header cell. - */ - public function display_header() { - echo ''; - $sortable = $this->is_sortable(); - $name = get_class($this); - $title = $this->get_title(); - $tip = $this->get_title_tip(); - if (is_array($sortable)) { - if ($title) { - echo '
' . $title . '
'; - } - $links = array(); - foreach ($sortable as $subsort => $details) { - $links[] = $this->make_sort_link($name . '-' . $subsort, - $details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse'])); - } - echo '
' . implode(' / ', $links) . '
'; - } else if ($sortable) { - echo $this->make_sort_link($name, $title, $tip); - } else { - if ($tip) { - echo ''; - } - echo $title; - if ($tip) { - echo ''; - } - } - echo "\n"; - } - - /** - * Title for this column. Not used if is_sortable returns an array. - */ - protected abstract function get_title(); - - /** - * @return string a fuller version of the name. Use this when get_title() returns - * something very short, and you want a longer version as a tool tip. - */ - protected function get_title_tip() { - return ''; - } - - /** - * Get a link that changes the sort order, and indicates the current sort state. - * @param string $sort the column to sort on. - * @param string $title the link text. - * @param string $tip the link tool-tip text. If empty, defaults to title. - * @param bool $defaultreverse whether the default sort order for this column is descending, rather than ascending. - * @return string HTML fragment. - */ - protected function make_sort_link($sort, $title, $tip, $defaultreverse = false) { - $currentsort = $this->qbank->get_primary_sort_order($sort); - $newsortreverse = $defaultreverse; - if ($currentsort) { - $newsortreverse = $currentsort > 0; - } - if (!$tip) { - $tip = $title; - } - if ($newsortreverse) { - $tip = get_string('sortbyxreverse', '', $tip); - } else { - $tip = get_string('sortbyx', '', $tip); - } - $link = ''; - $link .= $title; - if ($currentsort) { - $link .= $this->get_sort_icon($currentsort < 0); - } - $link .= ''; - return $link; - } - - /** - * Get an icon representing the corrent sort state. - * @param bool $reverse sort is descending, not ascending. - * @return string HTML image tag. - */ - protected function get_sort_icon($reverse) { - global $OUTPUT; - if ($reverse) { - return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', array('class' => 'iconsort')); - } else { - return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', array('class' => 'iconsort')); - } - } - - /** - * Output 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. - */ - public function display($question, $rowclasses) { - $this->display_start($question, $rowclasses); - $this->display_content($question, $rowclasses); - $this->display_end($question, $rowclasses); - } - - /** - * Output the opening column tag. If it is set as heading, it will use tag instead of - * - * @param \stdClass $question - * @param string $rowclasses - */ - protected function display_start($question, $rowclasses) { - $tag = 'td'; - $attr = array('class' => $this->get_classes()); - if ($this->isheading) { - $tag = 'th'; - $attr['scope'] = 'row'; - } - echo \html_writer::start_tag($tag, $attr); - } - - /** - * @return string the CSS classes to apply to every cell in this column. - */ - protected function get_classes() { - $classes = $this->get_extra_classes(); - $classes[] = $this->get_name(); - return implode(' ', $classes); - } - - /** - * Get the internal name for this column. Used as a CSS class name, - * and to store information about the current sort. Must match PARAM_ALPHA. - * - * @return string column name. - */ - public abstract function get_name(); - - /** - * @return array any extra class names you would like applied to every cell in this column. - */ - public function get_extra_classes() { - return array(); - } - - /** - * 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 abstract function display_content($question, $rowclasses); - - /** - * Output the closing column tag - * - * @param object $question - * @param string $rowclasses - */ - protected function display_end($question, $rowclasses) { - $tag = 'td'; - if ($this->isheading) { - $tag = 'th'; - } - echo \html_writer::end_tag($tag); - } - - /** - * 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 importnat 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() { - return array(); - } - - /** - * @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() { - return array(); - } - - /** - * If this column needs extra data (e.g. tags) then load that here. - * - * The extra data should be added to the question object in the array. - * Probably a good idea to check that another column has not already - * loaded the data you want. - * - * @param \stdClass[] $questions the questions that will be displayed. - */ - public function load_additional_data(array $questions) { - } - - /** - * Load the tags for each question. - * - * Helper that can be used from {@see load_additional_data()}; - * - * @param array $questions - */ - public function load_question_tags(array $questions) { - $firstquestion = reset($questions); - if (isset($firstquestion->tags)) { - // Looks like tags are already loaded, so don't do it again. - return; - } - - // Load the tags. - $tagdata = \core_tag_tag::get_items_tags('core_question', 'question', - array_keys($questions)); - - // Add them to the question objects. - foreach ($tagdata as $questionid => $tags) { - $questions[$questionid]->tags = $tags; - } - } - - /** - * Can this column be sorted on? You can return either: - * + false for no (the default), - * + a field name, if sorting this column corresponds to sorting on that datbase field. - * + an array of subnames to sort on as follows - * return array( - * 'firstname' => array('field' => 'uc.firstname', 'title' => get_string('firstname')), - * 'lastname' => array('field' => 'uc.lastname', 'title' => get_string('lastname')), - * ); - * As well as field, and field, you can also add 'revers' => 1 if you want the default sort - * order to be DESC. - * @return mixed as above. - */ - public function is_sortable() { - return false; - } - - /** - * Helper method for building sort clauses. - * @param bool $reverse whether the normal direction should be reversed. - * @return string 'ASC' or 'DESC' - */ - protected function sortorder($reverse) { - if ($reverse) { - return ' DESC'; - } else { - return ' ASC'; - } - } - - /** - * @param bool $reverse Whether to sort in the reverse of the default sort order. - * @param string $subsort if is_sortable returns an array of subnames, then this will be - * one of those. Otherwise will be empty. - * @return string some SQL to go in the order by clause. - */ - public function sort_expression($reverse, $subsort) { - $sortable = $this->is_sortable(); - if (is_array($sortable)) { - if (array_key_exists($subsort, $sortable)) { - return $sortable[$subsort]['field'] . $this->sortorder($reverse); - } else { - throw new \coding_exception('Unexpected $subsort type: ' . $subsort); - } - } else if ($sortable) { - return $sortable . $this->sortorder($reverse); - } else { - throw new \coding_exception('sort_expression called on a non-sortable column.'); - } - } -} diff --git a/question/classes/bank/copy_action_column.php b/question/classes/bank/copy_action_column.php deleted file mode 100644 index 769b81ac26a..00000000000 --- a/question/classes/bank/copy_action_column.php +++ /dev/null @@ -1,66 +0,0 @@ -. - -/** - * Question bank column for the duplicate action icon. - * - * @package core_question - * @copyright 2013 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Question bank column for the duplicate action icon. - * - * @copyright 2013 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class copy_action_column extends menu_action_column_base { - /** @var string avoids repeated calls to get_string('duplicate'). */ - protected $strcopy; - - public function init() { - parent::init(); - $this->strcopy = get_string('duplicate'); - } - - public function get_name() { - return 'copyaction'; - } - - protected function get_url_icon_and_label(\stdClass $question): array { - if (!\question_bank::is_qtype_installed($question->qtype)) { - // It sometimes happens that people end up with junk questions - // in their question bank of a type that is no longer installed. - // We cannot do most actions on them, because that leads to errors. - return [null, null, null]; - } - - // To copy a question, you need permission to add a question in the same - // category as the existing question, and ability to access the details of - // the question being copied. - if (question_has_capability_on($question, 'add') && - (question_has_capability_on($question, 'edit') || question_has_capability_on($question, 'view'))) { - return [$this->qbank->copy_question_moodle_url($question->id), 't/copy', $this->strcopy]; - } - return [null, null, null]; - } -} diff --git a/question/classes/bank/creator_name_column.php b/question/classes/bank/creator_name_column.php deleted file mode 100644 index f190e15cc4b..00000000000 --- a/question/classes/bank/creator_name_column.php +++ /dev/null @@ -1,75 +0,0 @@ -. - -/** - * A column type for the name of the question creator. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A column type for the name of the question creator. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class creator_name_column extends column_base { - public function get_name() { - return 'creatorname'; - } - - protected function get_title() { - return get_string('createdby', 'question'); - } - - protected function display_content($question, $rowclasses) { - if (!empty($question->creatorfirstname) && !empty($question->creatorlastname)) { - $u = new \stdClass(); - $u = username_load_fields_from_object($u, $question, 'creator'); - $date = userdate($question->timecreated, get_string('strftimedatetime', 'langconfig')); - echo fullname($u) . '
' . \html_writer::tag('span', $date, array('class' => 'date')); - } - } - - public function get_extra_joins() { - return array('uc' => 'LEFT JOIN {user} uc ON uc.id = q.createdby'); - } - - public function get_required_fields() { - $allnames = \core_user\fields::get_name_fields(); - $requiredfields = array(); - foreach ($allnames as $allname) { - $requiredfields[] = 'uc.' . $allname . ' AS creator' . $allname; - } - $requiredfields[] = 'q.timecreated'; - return $requiredfields; - } - - public function is_sortable() { - return array( - 'firstname' => array('field' => 'uc.firstname', 'title' => get_string('firstname')), - 'lastname' => array('field' => 'uc.lastname', 'title' => get_string('lastname')), - 'timecreated' => array('field' => 'q.timecreated', 'title' => get_string('date')) - ); - } -} diff --git a/question/classes/bank/delete_action_column.php b/question/classes/bank/delete_action_column.php deleted file mode 100644 index f969dcc84f9..00000000000 --- a/question/classes/bank/delete_action_column.php +++ /dev/null @@ -1,82 +0,0 @@ -. - -/** - * action to delete (or hide) a question, or restore a previously hidden question. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * action to delete (or hide) a question, or restore a previously hidden question. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class delete_action_column extends menu_action_column_base { - protected $strdelete; - protected $strrestore; - - public function init() { - parent::init(); - $this->strdelete = get_string('delete'); - $this->strrestore = get_string('restore'); - } - - public function get_name() { - return 'deleteaction'; - } - - /** - * Work out the info required to display this action, if appropriate. - * - * If the action is not appropriate to this question, return [null, null, null]. - * - * Otherwise return an array with three elements: - * moodel_url $url the URL to perform the action. - * string $icon the icon name. E.g. 't/delete'. - * string $label the label to display. - * - * @param object $question the row from the $question table, augmented with extra information. - * @return array [$url, $label, $icon] as above. - */ - protected function get_url_icon_and_label(\stdClass $question): array { - if (!question_has_capability_on($question, 'edit')) { - return [null, null, null]; - } - if ($question->hidden) { - $url = new \moodle_url($this->qbank->base_url(), array('unhide' => $question->id, 'sesskey' => sesskey())); - return [$url, 't/restore', $this->strrestore]; - } else { - $url = new \moodle_url($this->qbank->base_url(), array('deleteselected' => $question->id, 'q' . $question->id => 1, - 'sesskey' => sesskey())); - return [$url, 't/delete', $this->strdelete]; - } - } - - public function get_required_fields() { - $required = parent::get_required_fields(); - $required[] = 'q.hidden'; - return $required; - } -} diff --git a/question/classes/bank/edit_action_column.php b/question/classes/bank/edit_action_column.php deleted file mode 100644 index 582ce80ae49..00000000000 --- a/question/classes/bank/edit_action_column.php +++ /dev/null @@ -1,66 +0,0 @@ -. - -/** - * Base class for question bank columns that just contain an action icon. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Base class for question bank columns that just contain an action icon. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class edit_action_column extends menu_action_column_base { - protected $stredit; - protected $strview; - - public function init() { - parent::init(); - $this->stredit = get_string('editquestion', 'question'); - $this->strview = get_string('view'); - } - - public function get_name() { - return 'editaction'; - } - - protected function get_url_icon_and_label(\stdClass $question): array { - if (!\question_bank::is_qtype_installed($question->qtype)) { - // It sometimes happens that people end up with junk questions - // in their question bank of a type that is no longer installed. - // We cannot do most actions on them, because that leads to errors. - return [null, null, null]; - } - - if (question_has_capability_on($question, 'edit')) { - return [$this->qbank->edit_question_moodle_url($question->id), 't/edit', $this->stredit]; - } else if (question_has_capability_on($question, 'view')) { - return [$this->qbank->edit_question_moodle_url($question->id), 'i/info', $this->strview]; - } else { - return [null, null, null]; - } - } -} diff --git a/question/classes/bank/edit_menu_column.php b/question/classes/bank/edit_menu_column.php deleted file mode 100644 index 687d0b8546e..00000000000 --- a/question/classes/bank/edit_menu_column.php +++ /dev/null @@ -1,102 +0,0 @@ -. - -/** - * A question bank column which gathers together all the actions into a menu. - * - * @package core_question - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A question bank column which gathers together all the actions into a menu. - * - * This question bank column, if added to the question bank, will - * replace all of the other columns which implement the - * {@link menuable_action} interface and replace them with a single - * column containing an Edit menu. - * - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -class edit_menu_column extends column_base { - /** - * @var menuable_action[] - */ - protected $actions; - - /** - * Set up the list of actions that should be shown in the menu. - * - * This takes a list of column object (the list from a question - * bank view). It extracts all the ones that should go in the menu - * and stores them for later use. Then it returns the remaining columns. - * - * @param column_base[] $allcolumns a set of columns. - * @return column_base[] the non-action columns from the set. - */ - public function claim_menuable_columns($allcolumns) { - $remainingcolumns = []; - foreach ($allcolumns as $key => $column) { - if ($column instanceof menuable_action) { - $this->actions[$key] = $column; - } else { - $remainingcolumns[$key] = $column; - } - } - return $remainingcolumns; - } - - protected function get_title() { - return get_string('actions'); - } - - public function get_name() { - return 'editmenu'; - } - - protected function display_content($question, $rowclasses) { - global $OUTPUT; - - $menu = new \action_menu(); - $menu->set_menu_trigger(get_string('edit')); - $menu->set_alignment(\action_menu::TL, \action_menu::BL); - foreach ($this->actions as $actioncolumn) { - $action = $actioncolumn->get_action_menu_link($question); - if ($action) { - $menu->add($action); - } - } - - $qtypeactions = \question_bank::get_qtype($question->qtype, false) - ->get_extra_question_bank_actions($question); - foreach ($qtypeactions as $action) { - $menu->add($action); - } - - echo $OUTPUT->render($menu); - } - - public function get_required_fields() { - return ['q.qtype']; - } -} diff --git a/question/classes/bank/export_xml_action_column.php b/question/classes/bank/export_xml_action_column.php deleted file mode 100644 index 55cbd250483..00000000000 --- a/question/classes/bank/export_xml_action_column.php +++ /dev/null @@ -1,64 +0,0 @@ -. - -/** - * Question bank column export the question in Moodle XML format. - * - * @package core_question - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Question bank column export the question in Moodle XML format. - * - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class export_xml_action_column extends menu_action_column_base { - /** @var string avoids repeated calls to get_string('duplicate'). */ - protected $strexportasxml; - - public function init() { - parent::init(); - $this->strexportasxml = get_string('exportasxml', 'question'); - } - - public function get_name() { - return 'exportasxmlaction'; - } - - protected function get_url_icon_and_label(\stdClass $question): array { - if (!\question_bank::is_qtype_installed($question->qtype)) { - // It sometimes happens that people end up with junk questions - // in their question bank of a type that is no longer installed. - // We cannot do most actions on them, because that leads to errors. - return [null, null, null]; - } - - if (!question_has_capability_on($question, 'view')) { - return [null, null, null]; - } - - return [\qbank_exporttoxml\helper::question_get_export_single_question_url($question), - 't/download', $this->strexportasxml]; - } -} diff --git a/question/classes/bank/menu_action_column_base.php b/question/classes/bank/menu_action_column_base.php deleted file mode 100644 index c730bd7dfdc..00000000000 --- a/question/classes/bank/menu_action_column_base.php +++ /dev/null @@ -1,71 +0,0 @@ -. - -/** - * Base class to make it easier to implement actions that are menuable_actions. - * - * @package core_question - * @copyright 2019 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Base class to make it easier to implement actions that are menuable_actions. - * - * Use this class if your action is simple (defined by just a URL, label and icon). - * If your action is not simple enough to fit into the pattern that this - * class implements, then you will have to implement the menuable_action - * interface yourself. - * - * @copyright 2019 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -abstract class menu_action_column_base extends action_column_base implements menuable_action { - - /** - * Get the information required to display this action either as a menu item or a separate action column. - * - * If this action cannot apply to this question (e.g. because the user does not have - * permission, then return [null, null, null]. - * - * @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. E.g. 't/delete'. - * $label - text label to display in the UI (either in the menu, or as a tool-tip on the icon) - */ - abstract protected function get_url_icon_and_label(\stdClass $question): array; - - protected function display_content($question, $rowclasses) { - [$url, $icon, $label] = $this->get_url_icon_and_label($question); - if ($url) { - $this->print_icon($icon, $label, $url); - } - } - - public function get_action_menu_link(\stdClass $question): ?\action_menu_link { - [$url, $icon, $label] = $this->get_url_icon_and_label($question); - if (!$url) { - return null; - } - return new \action_menu_link_secondary($url, new \pix_icon($icon, ''), $label); - } -} diff --git a/question/classes/bank/menuable_action.php b/question/classes/bank/menuable_action.php deleted file mode 100644 index 2a4278cc157..00000000000 --- a/question/classes/bank/menuable_action.php +++ /dev/null @@ -1,57 +0,0 @@ -. - -/** - * Interface to indicate that a question bank column can go in the action menu. - * - * @package core_question - * @copyright 2019 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Interface to indicate that a question bank column can go in the action menu. - * - * If a question bank column implements this interface, and if the {@link edit_menu_column} - * is present in the question bank view, then the 'column' will be shown as an entry in the - * edit menu instead of as a separate column. - * - * Probably most columns that want to implement this will be subclasses of - * {@link action_column_base}, and most such columns should probably implement - * this interface. - * - * If your column is a simple action, you can probably save duplicated code by - * using the base class action_column_menuable as an easy way to implement both - * action_column_base and this interface. - * - * @copyright 2019 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -interface menuable_action { - - /** - * 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; -} diff --git a/question/classes/bank/modifier_name_column.php b/question/classes/bank/modifier_name_column.php deleted file mode 100644 index 3b7621ff287..00000000000 --- a/question/classes/bank/modifier_name_column.php +++ /dev/null @@ -1,75 +0,0 @@ -. - -/** - * A column type for the name of the question last modifier. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A column type for the name of the question last modifier. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class modifier_name_column extends column_base { - public function get_name() { - return 'modifiername'; - } - - protected function get_title() { - return get_string('lastmodifiedby', 'question'); - } - - protected function display_content($question, $rowclasses) { - if (!empty($question->modifierfirstname) && !empty($question->modifierlastname)) { - $u = new \stdClass(); - $u = username_load_fields_from_object($u, $question, 'modifier'); - $date = userdate($question->timemodified, get_string('strftimedatetime', 'langconfig')); - echo fullname($u) . '
' . \html_writer::tag('span', $date, array('class' => 'date')); - } - } - - public function get_extra_joins() { - return array('um' => 'LEFT JOIN {user} um ON um.id = q.modifiedby'); - } - - public function get_required_fields() { - $allnames = \core_user\fields::get_name_fields(); - $requiredfields = array(); - foreach ($allnames as $allname) { - $requiredfields[] = 'um.' . $allname . ' AS modifier' . $allname; - } - $requiredfields[] = 'q.timemodified'; - return $requiredfields; - } - - public function is_sortable() { - return array( - 'firstname' => array('field' => 'um.firstname', 'title' => get_string('firstname')), - 'lastname' => array('field' => 'um.lastname', 'title' => get_string('lastname')), - 'timemodified' => array('field' => 'q.timemodified', 'title' => get_string('date')) - ); - } -} diff --git a/question/classes/bank/preview_action_column.php b/question/classes/bank/preview_action_column.php deleted file mode 100644 index 54469e0e2ed..00000000000 --- a/question/classes/bank/preview_action_column.php +++ /dev/null @@ -1,84 +0,0 @@ -. - -/** - * Question bank columns for the preview action icon. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Question bank columns for the preview action icon. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class preview_action_column extends action_column_base implements menuable_action { - /** - * @var string store this lang string for performance. - */ - protected $strpreview; - - public function init() { - parent::init(); - $this->strpreview = get_string('preview'); - } - - public function get_name() { - return 'previewaction'; - } - - protected function display_content($question, $rowclasses) { - global $PAGE; - - if (!\question_bank::is_qtype_installed($question->qtype)) { - // It sometimes happens that people end up with junk questions - // in their question bank of a type that is no longer installed. - // We cannot do most actions on them, because that leads to errors. - return; - } - - if (question_has_capability_on($question, 'use')) { - echo $PAGE->get_renderer('core_question')->question_preview_link( - $question->id, $this->qbank->get_most_specific_context(), false); - } - } - - public function get_action_menu_link(\stdClass $question): ?\action_menu_link { - if (!\question_bank::is_qtype_installed($question->qtype)) { - // It sometimes happens that people end up with junk questions - // in their question bank of a type that is no longer installed. - // We cannot do most actions on them, because that leads to errors. - return null; - } - - if (!question_has_capability_on($question, 'use')) { - return null; - } - - $context = $this->qbank->get_most_specific_context(); - $url = question_preview_url($question->id, null, null, null, null, $context); - return new \action_menu_link_secondary($url, new \pix_icon('t/preview', ''), - $this->strpreview, ['target' => 'questionpreview']); - } -} diff --git a/question/classes/bank/question_name_column.php b/question/classes/bank/question_name_column.php deleted file mode 100644 index 2fd578b2449..00000000000 --- a/question/classes/bank/question_name_column.php +++ /dev/null @@ -1,76 +0,0 @@ -. - -/** - * A column type for the name of the question name. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A column type for the name of the question name. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class question_name_column extends column_base { - protected $checkboxespresent = null; - - public function get_name() { - return 'questionname'; - } - - protected function get_title() { - return get_string('question'); - } - - protected function label_for($question) { - if (is_null($this->checkboxespresent)) { - $this->checkboxespresent = $this->qbank->has_column('core_question\local\bank\checkbox_column'); - } - if ($this->checkboxespresent) { - return 'checkq' . $question->id; - } else { - return ''; - } - } - - protected function display_content($question, $rowclasses) { - $labelfor = $this->label_for($question); - if ($labelfor) { - echo ''; - } - } - - public function get_required_fields() { - return array('q.id', 'q.name'); - } - - public function is_sortable() { - return 'q.name'; - } -} diff --git a/question/classes/bank/question_name_idnumber_tags_column.php b/question/classes/bank/question_name_idnumber_tags_column.php deleted file mode 100644 index d026a0dc40b..00000000000 --- a/question/classes/bank/question_name_idnumber_tags_column.php +++ /dev/null @@ -1,90 +0,0 @@ -. - -/** - * A question bank column showing the question name with idnumber and tags. - * - * @package core_question - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A question bank column showing the question name with idnumber and tags. - * - * @copyright 2019 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class question_name_idnumber_tags_column extends question_name_column { - public function get_name() { - return 'qnameidnumbertags'; - } - - protected function display_content($question, $rowclasses) { - global $OUTPUT; - - $layoutclasses = 'd-inline-flex flex-nowrap overflow-hidden w-100'; - $labelfor = $this->label_for($question); - if ($labelfor) { - echo ''; - } else { - echo ''; - $closetag = ''; - } - - // Question name. - echo \html_writer::span(format_string($question->name), 'questionname flex-grow-1 flex-shrink-1 text-truncate'); - - // Question idnumber. - if ($question->idnumber !== null && $question->idnumber !== '') { - echo ' ' . \html_writer::span( - \html_writer::span(get_string('idnumber', 'question'), 'accesshide') . ' ' . - \html_writer::span(s($question->idnumber), 'badge badge-primary'), 'ml-1'); - } - - // Question tags. - if (!empty($question->tags)) { - $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id); - echo $OUTPUT->tag_list($tags, null, 'd-inline flex-shrink-1 text-truncate ml-1', 0, null, true); - } - - echo $closetag; // Computed above to ensure it matches. - } - - public function get_required_fields() { - $fields = parent::get_required_fields(); - $fields[] = 'q.idnumber'; - return $fields; - } - - public function is_sortable() { - return [ - 'name' => ['field' => 'q.name', 'title' => get_string('questionname', 'question')], - 'idnumber' => ['field' => 'q.idnumber', 'title' => get_string('idnumber', 'question')], - ]; - } - - public function load_additional_data(array $questions) { - parent::load_additional_data($questions); - parent::load_question_tags($questions); - } -} diff --git a/question/classes/bank/question_text_row.php b/question/classes/bank/question_text_row.php deleted file mode 100644 index 626083999a0..00000000000 --- a/question/classes/bank/question_text_row.php +++ /dev/null @@ -1,72 +0,0 @@ -. - -/** - * A column type for the name of the question name. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A column type for the name of the question name. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class question_text_row extends row_base { - protected $formatoptions; - - protected function init() { - $this->formatoptions = new \stdClass(); - $this->formatoptions->noclean = true; - $this->formatoptions->para = false; - } - - public function get_name() { - return 'questiontext'; - } - - protected function get_title() { - return get_string('questiontext', 'question'); - } - - protected function display_content($question, $rowclasses) { - $text = question_rewrite_question_preview_urls($question->questiontext, $question->id, - $question->contextid, 'question', 'questiontext', $question->id, - $question->contextid, 'core_question'); - $text = format_text($text, $question->questiontextformat, - $this->formatoptions); - if ($text == '') { - $text = ' '; - } - echo $text; - } - - public function get_extra_joins() { - return array('qc' => 'JOIN {question_categories} qc ON qc.id = q.category'); - } - - public function get_required_fields() { - return array('q.id', 'q.questiontext', 'q.questiontextformat', 'qc.contextid'); - } -} diff --git a/question/classes/bank/question_type_column.php b/question/classes/bank/question_type_column.php deleted file mode 100644 index cf2f18ecf43..00000000000 --- a/question/classes/bank/question_type_column.php +++ /dev/null @@ -1,60 +0,0 @@ -. - -/** - * A column type for the name of the question type. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * A column type for the name of the question type. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class question_type_column extends column_base { - public function get_name() { - return 'qtype'; - } - - protected function get_title() { - return get_string('qtypeveryshort', 'question'); - } - - protected function get_title_tip() { - return get_string('questiontype', 'question'); - } - - protected function display_content($question, $rowclasses) { - echo print_question_icon($question); - } - - public function get_required_fields() { - return array('q.qtype'); - } - - public function is_sortable() { - return 'q.qtype'; - } -} diff --git a/question/classes/bank/random_question_loader.php b/question/classes/bank/random_question_loader.php deleted file mode 100644 index 9dac3ac9128..00000000000 --- a/question/classes/bank/random_question_loader.php +++ /dev/null @@ -1,323 +0,0 @@ -. - -/** - * A class for efficiently finds questions at random from the question bank. - * - * @package core_question - * @copyright 2015 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; - - -/** - * This class efficiently finds questions at random from the question bank. - * - * You can ask for questions at random one at a time. Each time you ask, you - * pass a category id, and whether to pick from that category and all subcategories - * or just that category. - * - * The number of teams each question has been used is tracked, and we will always - * return a question from among those elegible that has been used the fewest times. - * So, if there are questions that have not been used yet in the category asked for, - * one of those will be returned. However, within one instantiation of this class, - * we will never return a given question more than once, and we will never return - * questions passed into the constructor as $usedquestions. - * - * @copyright 2015 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -class random_question_loader { - /** @var \qubaid_condition which usages to consider previous attempts from. */ - protected $qubaids; - - /** @var array qtypes that cannot be used by random questions. */ - protected $excludedqtypes; - - /** @var array categoryid & include subcategories => num previous uses => questionid => 1. */ - protected $availablequestionscache = array(); - - /** - * @var array questionid => num recent uses. Questions that have been used, - * but that is not yet recorded in the DB. - */ - protected $recentlyusedquestions; - - /** - * Constructor. - * @param \qubaid_condition $qubaids the usages to consider when counting previous uses of each question. - * @param array $usedquestions questionid => number of times used count. If we should allow for - * further existing uses of a question in addition to the ones in $qubaids. - */ - public function __construct(\qubaid_condition $qubaids, array $usedquestions = array()) { - $this->qubaids = $qubaids; - $this->recentlyusedquestions = $usedquestions; - - foreach (\question_bank::get_all_qtypes() as $qtype) { - if (!$qtype->is_usable_by_random()) { - $this->excludedqtypes[] = $qtype->name(); - } - } - } - - /** - * Pick a question at random from the given category, from among those with the fewest uses. - * If an array of tag ids are specified, then only the questions that are tagged with ALL those tags will be selected. - * - * It is up the the caller to verify that the cateogry exists. An unknown category - * behaves like an empty one. - * - * @param int $categoryid the id of a category in the question bank. - * @param bool $includesubcategories wether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids An array of tag ids. A question has to be tagged with all the provided tagids (if any) - * in order to be eligible for being picked. - * @return int|null the id of the question picked, or null if there aren't any. - */ - public function get_next_question_id($categoryid, $includesubcategories, $tagids = []) { - $this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids); - - $categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids); - if (empty($this->availablequestionscache[$categorykey])) { - return null; - } - - reset($this->availablequestionscache[$categorykey]); - $lowestcount = key($this->availablequestionscache[$categorykey]); - reset($this->availablequestionscache[$categorykey][$lowestcount]); - $questionid = key($this->availablequestionscache[$categorykey][$lowestcount]); - $this->use_question($questionid); - return $questionid; - } - - /** - * Get the key into {@link $availablequestionscache} for this combination of options. - * @param int $categoryid the id of a category in the question bank. - * @param bool $includesubcategories wether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids an array of tag ids. - * @return string the cache key. - */ - protected function get_category_key($categoryid, $includesubcategories, $tagids = []) { - if ($includesubcategories) { - $key = $categoryid . '|1'; - } else { - $key = $categoryid . '|0'; - } - - if (!empty($tagids)) { - $key .= '|' . implode('|', $tagids); - } - - return $key; - } - - /** - * Populate {@link $availablequestionscache} for this combination of options. - * @param int $categoryid The id of a category in the question bank. - * @param bool $includesubcategories Whether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids An array of tag ids. If an array is provided, then - * only the questions that are tagged with ALL the provided tagids will be loaded. - */ - protected function ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids = []) { - global $DB; - - $categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids); - - if (isset($this->availablequestionscache[$categorykey])) { - // Data is already in the cache, nothing to do. - return; - } - - // Load the available questions from the question bank. - if ($includesubcategories) { - $categoryids = question_categorylist($categoryid); - } else { - $categoryids = array($categoryid); - } - - list($extraconditions, $extraparams) = $DB->get_in_or_equal($this->excludedqtypes, - SQL_PARAMS_NAMED, 'excludedqtype', false); - - $questionidsandcounts = \question_bank::get_finder()->get_questions_from_categories_and_tags_with_usage_counts( - $categoryids, $this->qubaids, 'q.qtype ' . $extraconditions, $extraparams, $tagids); - if (!$questionidsandcounts) { - // No questions in this category. - $this->availablequestionscache[$categorykey] = array(); - return; - } - - // Put all the questions with each value of $prevusecount in separate arrays. - $idsbyusecount = array(); - foreach ($questionidsandcounts as $questionid => $prevusecount) { - if (isset($this->recentlyusedquestions[$questionid])) { - // Recently used questions are never returned. - continue; - } - $idsbyusecount[$prevusecount][] = $questionid; - } - - // Now put that data into our cache. For each count, we need to shuffle - // questionids, and make those the keys of an array. - $this->availablequestionscache[$categorykey] = array(); - foreach ($idsbyusecount as $prevusecount => $questionids) { - shuffle($questionids); - $this->availablequestionscache[$categorykey][$prevusecount] = array_combine( - $questionids, array_fill(0, count($questionids), 1)); - } - ksort($this->availablequestionscache[$categorykey]); - } - - /** - * Update the internal data structures to indicate that a given question has - * been used one more time. - * - * @param int $questionid the question that is being used. - */ - protected function use_question($questionid) { - if (isset($this->recentlyusedquestions[$questionid])) { - $this->recentlyusedquestions[$questionid] += 1; - } else { - $this->recentlyusedquestions[$questionid] = 1; - } - - foreach ($this->availablequestionscache as $categorykey => $questionsforcategory) { - foreach ($questionsforcategory as $numuses => $questionids) { - if (!isset($questionids[$questionid])) { - continue; - } - unset($this->availablequestionscache[$categorykey][$numuses][$questionid]); - if (empty($this->availablequestionscache[$categorykey][$numuses])) { - unset($this->availablequestionscache[$categorykey][$numuses]); - } - } - } - } - - /** - * Get the list of available question ids for the given criteria. - * - * @param int $categoryid The id of a category in the question bank. - * @param bool $includesubcategories Whether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids An array of tag ids. If an array is provided, then - * only the questions that are tagged with ALL the provided tagids will be loaded. - * @return int[] The list of question ids - */ - protected function get_question_ids($categoryid, $includesubcategories, $tagids = []) { - $this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids); - $categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids); - $cachedvalues = $this->availablequestionscache[$categorykey]; - $questionids = []; - - foreach ($cachedvalues as $usecount => $ids) { - $questionids = array_merge($questionids, array_keys($ids)); - } - - return $questionids; - } - - /** - * Check whether a given question is available in a given category. If so, mark it used. - * If an optional list of tag ids are provided, then the question must be tagged with - * ALL of the provided tags to be considered as available. - * - * @param int $categoryid the id of a category in the question bank. - * @param bool $includesubcategories wether to pick a question from exactly - * that category, or that category and subcategories. - * @param int $questionid the question that is being used. - * @param array $tagids An array of tag ids. Only the questions that are tagged with all the provided tagids can be available. - * @return bool whether the question is available in the requested category. - */ - public function is_question_available($categoryid, $includesubcategories, $questionid, $tagids = []) { - $this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids); - $categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids); - - foreach ($this->availablequestionscache[$categorykey] as $questionids) { - if (isset($questionids[$questionid])) { - $this->use_question($questionid); - return true; - } - } - - return false; - } - - /** - * Get the list of available questions for the given criteria. - * - * @param int $categoryid The id of a category in the question bank. - * @param bool $includesubcategories Whether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids An array of tag ids. If an array is provided, then - * only the questions that are tagged with ALL the provided tagids will be loaded. - * @param int $limit Maximum number of results to return. - * @param int $offset Number of items to skip from the begging of the result set. - * @param string[] $fields The fields to return for each question. - * @return \stdClass[] The list of question records - */ - public function get_questions( - $categoryid, - $includesubcategories, - $tagids = [], - $limit = 100, - $offset = 0, - $fields = [] - ) { - global $DB; - - $questionids = $this->get_question_ids($categoryid, $includesubcategories, $tagids); - if (empty($questionids)) { - return []; - } - - if (empty($fields)) { - // Return all fields. - $fieldsstring = '*'; - } else { - $fieldsstring = implode(',', $fields); - } - - return $DB->get_records_list( - 'question', - 'id', - $questionids, - 'id', - $fieldsstring, - $offset, - $limit - ); - } - - /** - * Count the number of available questions for the given criteria. - * - * @param int $categoryid The id of a category in the question bank. - * @param bool $includesubcategories Whether to pick a question from exactly - * that category, or that category and subcategories. - * @param array $tagids An array of tag ids. If an array is provided, then - * only the questions that are tagged with ALL the provided tagids will be loaded. - * @return int The number of questions matching the criteria. - */ - public function count_questions($categoryid, $includesubcategories, $tagids = []) { - $questionids = $this->get_question_ids($categoryid, $includesubcategories, $tagids); - return count($questionids); - } -} diff --git a/question/classes/bank/row_base.php b/question/classes/bank/row_base.php deleted file mode 100644 index 7f9363ca40b..00000000000 --- a/question/classes/bank/row_base.php +++ /dev/null @@ -1,53 +0,0 @@ -. - -/** - * Base class for 'columns' that are actually displayed as a row following the main question row. - * - * @package core_question - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Base class for 'columns' that are actually displayed as a row following the main question row. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -abstract class row_base extends column_base { - public function is_extra_row() { - return true; - } - - protected function display_start($question, $rowclasses) { - if ($rowclasses) { - echo '' . "\n"; - } else { - echo "\n"; - } - echo ''; - } - - protected function display_end($question, $rowclasses) { - echo "\n"; - } -} diff --git a/question/classes/bank/search/category_condition.php b/question/classes/bank/search/category_condition.php index a47ff1ec2fe..3dcb84c27cb 100644 --- a/question/classes/bank/search/category_condition.php +++ b/question/classes/bank/search/category_condition.php @@ -122,7 +122,10 @@ class category_condition extends condition { true, -1, false); $displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [], array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory')); - $displaydata['categorydesc'] = $this->print_category_info($this->category); + $displaydata['categorydesc'] = ''; + if ($this->category) { + $displaydata['categorydesc'] = $this->print_category_info($this->category); + } return $PAGE->get_renderer('core_question', 'bank')->render_category_condition($displaydata); } diff --git a/question/classes/bank/tags_action_column.php b/question/classes/bank/tags_action_column.php deleted file mode 100644 index 991e2315f54..00000000000 --- a/question/classes/bank/tags_action_column.php +++ /dev/null @@ -1,109 +0,0 @@ -. - -/** - * The question tags column subclass. - * - * @package core_question - * @copyright 2018 Simey Lameze - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - - -/** - * Action to add and remove tags to questions. - * - * @copyright 2018 Simey Lameze - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class tags_action_column extends action_column_base implements menuable_action { - /** - * @var string store this lang string for performance. - */ - protected $managetags; - - public function init() { - parent::init(); - // Removed for conflicting js calls. - // ...global $CFG;. - // ...if ($CFG->usetags) {. - // ...global $PAGE;. - // ...$PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);. - // ...}. - $this->managetags = get_string('managetags', 'tag'); - } - - /** - * Return the name for this column. - * - * @return string - */ - public function get_name() { - return 'tagsaction'; - } - - /** - * Display tags column content. - * - * @param object $question The question database record. - * @param string $rowclasses - */ - protected function display_content($question, $rowclasses) { - global $OUTPUT; - - if (\core_tag_tag::is_enabled('core_question', 'question') && - question_has_capability_on($question, 'view')) { - - [$url, $attributes] = $this->get_link_url_and_attributes($question); - echo \html_writer::link($url, $OUTPUT->pix_icon('t/tags', - $this->managetags), $attributes); - } - } - - /** - * Helper used by display_content and get_action_menu_link. - * - * @param object $question the row from the $question table, augmented with extra information. - * @return array with two elements, \moodle_url and - * an array or data $attributes needed to make the JavaScript work. - */ - protected function get_link_url_and_attributes($question) { - $url = new \moodle_url($this->qbank->edit_question_url($question->id)); - - $attributes = [ - 'data-action' => 'edittags', - 'data-cantag' => question_has_capability_on($question, 'tag'), - 'data-contextid' => $this->qbank->get_most_specific_context()->id, - 'data-questionid' => $question->id - ]; - - return [$url, $attributes]; - } - - public function get_action_menu_link(\stdClass $question): ?\action_menu_link { - if (!\core_tag_tag::is_enabled('core_question', 'question') || - !question_has_capability_on($question, 'view')) { - return null; - } - - [$url, $attributes] = $this->get_link_url_and_attributes($question); - return new \action_menu_link_secondary($url, new \pix_icon('t/tags', ''), - $this->managetags, $attributes); - } -} diff --git a/question/classes/bank/view.php b/question/classes/bank/view.php deleted file mode 100644 index 711fba5607f..00000000000 --- a/question/classes/bank/view.php +++ /dev/null @@ -1,1104 +0,0 @@ -. - - -/** - * Class to print a view of the question bank. - * - * @package core_question - * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\bank; -defined('MOODLE_INTERNAL') || die(); - -use core_question\bank\search\condition; - - -/** - * This class prints a view of the question bank, including - * + Some controls to allow users to to select what is displayed. - * + A list of questions as a table. - * + Further controls to do things with the questions. - * - * This class gives a basic view, and provides plenty of hooks where subclasses - * can override parts of the display. - * - * The list of questions presented as a table is generated by creating a list of - * core_question\bank\column objects, one for each 'column' to be displayed. These - * manage - * + outputting the contents of that column, given a $question object, but also - * + generating the right fragments of SQL to ensure the necessary data is present, - * and sorted in the right order. - * + outputting table headers. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank - */ -class view { - const MAX_SORTS = 3; - - /** - * @var \moodle_url base URL for the current page. Used as the - * basis for making URLs for actions that reload the page. - */ - protected $baseurl; - - /** - * @var \moodle_url used as a basis for URLs that edit a question. - */ - protected $editquestionurl; - - /** - * @var \question_edit_contexts - */ - protected $contexts; - - /** - * @var object|\cm_info|null if we are in a module context, the cm. - */ - protected $cm; - - /** - * @var object the course we are within. - */ - protected $course; - - /** - * @var \question_bank_column_base[] these are all the 'columns' that are - * part of the display. Array keys are the class name. - */ - protected $requiredcolumns; - - /** - * @var \question_bank_column_base[] these are the 'columns' that are - * actually displayed as a column, in order. Array keys are the class name. - */ - protected $visiblecolumns; - - /** - * @var \question_bank_column_base[] these are the 'columns' that are - * actually displayed as an additional row (e.g. question text), in order. - * Array keys are the class name. - */ - protected $extrarows; - - /** - * @var array list of column class names for which columns to sort on. - */ - protected $sort; - - /** - * @var int|null id of the a question to highlight in the list (if present). - */ - protected $lastchangedid; - - /** - * @var string SQL to count the number of questions matching the current - * search conditions. - */ - protected $countsql; - - /** - * @var string SQL to actually load the question data to display. - */ - protected $loadsql; - - /** - * @var array params used by $countsql and $loadsql (which currently must be the same). - */ - protected $sqlparams; - - /** - * @var condition[] search conditions. - */ - protected $searchconditions = array(); - - /** - * Constructor - * @param \question_edit_contexts $contexts - * @param \moodle_url $pageurl - * @param object $course course settings - * @param object $cm (optional) activity settings. - */ - public function __construct($contexts, $pageurl, $course, $cm = null) { - $this->contexts = $contexts; - $this->baseurl = $pageurl; - $this->course = $course; - $this->cm = $cm; - - // Create the url of the new question page to forward to. - $returnurl = $pageurl->out_as_local_url(false); - $this->editquestionurl = new \moodle_url('/question/question.php', - array('returnurl' => $returnurl)); - if ($cm !== null) { - $this->editquestionurl->param('cmid', $cm->id); - } else { - $this->editquestionurl->param('courseid', $this->course->id); - } - - $this->lastchangedid = optional_param('lastchanged', 0, PARAM_INT); - - $this->init_columns($this->wanted_columns(), $this->heading_column()); - $this->init_sort(); - $this->init_search_conditions(); - } - - /** - * Initialize search conditions from plugins - * local_*_get_question_bank_search_conditions() must return an array of - * \core_question\bank\search\condition objects. - */ - protected function init_search_conditions() { - $searchplugins = get_plugin_list_with_function('local', 'get_question_bank_search_conditions'); - foreach ($searchplugins as $component => $function) { - foreach ($function($this) as $searchobject) { - $this->add_searchcondition($searchobject); - } - } - } - - protected function wanted_columns() { - global $CFG; - - if (empty($CFG->questionbankcolumns)) { - $questionbankcolumns = array('checkbox_column', 'question_type_column', - 'question_name_idnumber_tags_column', 'edit_menu_column', - 'edit_action_column', 'copy_action_column', 'tags_action_column', - 'preview_action_column', 'delete_action_column', 'export_xml_action_column', - 'creator_name_column', 'modifier_name_column'); - } else { - $questionbankcolumns = explode(',', $CFG->questionbankcolumns); - } - if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) { - $questionbankcolumns[] = 'question_text_row'; - } - - foreach ($questionbankcolumns as $fullname) { - if (! class_exists($fullname)) { - if (class_exists('core_question\\bank\\' . $fullname)) { - $fullname = 'core_question\\bank\\' . $fullname; - } else { - throw new \coding_exception("No such class exists: $fullname"); - } - } - $this->requiredcolumns[$fullname] = new $fullname($this); - } - return $this->requiredcolumns; - } - - - /** - * Get a column object from its name. - * - * @param string $columnname. - * @return \core_question\bank\column_base. - */ - protected function get_column_type($columnname) { - if (! class_exists($columnname)) { - if (class_exists('core_question\\bank\\' . $columnname)) { - $columnname = 'core_question\\bank\\' . $columnname; - } else { - throw new \coding_exception("No such class exists: $columnname"); - } - } - if (empty($this->requiredcolumns[$columnname])) { - $this->requiredcolumns[$columnname] = new $columnname($this); - } - return $this->requiredcolumns[$columnname]; - } - - /** - * Specify the column heading - * - * @return string Column name for the heading - */ - protected function heading_column() { - return 'question_bank_question_name_column'; - } - - /** - * Initializing table columns - * - * @param array $wanted Collection of column names - * @param string $heading The name of column that is set as heading - */ - protected function init_columns($wanted, $heading = '') { - // If we are using the edit menu column, allow it to absorb all the actions. - foreach ($wanted as $column) { - if ($column instanceof edit_menu_column) { - $wanted = $column->claim_menuable_columns($wanted); - break; - } - } - - // Now split columns into real columns and rows. - $this->visiblecolumns = array(); - $this->extrarows = array(); - foreach ($wanted as $column) { - if ($column->is_extra_row()) { - $this->extrarows[get_class($column)] = $column; - } else { - $this->visiblecolumns[get_class($column)] = $column; - } - } - if (array_key_exists($heading, $this->requiredcolumns)) { - $this->requiredcolumns[$heading]->set_as_heading(); - } - } - - /** - * @param string $colname a column internal name. - * @return bool is this column included in the output? - */ - public function has_column($colname) { - return isset($this->visiblecolumns[$colname]); - } - - /** - * @return int The number of columns in the table. - */ - public function get_column_count() { - return count($this->visiblecolumns); - } - - public function get_courseid() { - return $this->course->id; - } - - protected function init_sort() { - $this->init_sort_from_params(); - if (empty($this->sort)) { - $this->sort = $this->default_sort(); - } - } - - /** - * 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) { - // Do the parsing. - if (strpos($sort, '-') !== false) { - list($colname, $subsort) = explode('-', $sort, 2); - } else { - $colname = $sort; - $subsort = ''; - } - // Validate the column name. - $column = $this->get_column_type($colname); - if (!isset($column) || !$column->is_sortable()) { - for ($i = 1; $i <= self::MAX_SORTS; $i++) { - $this->baseurl->remove_params('qbs' . $i); - } - throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $colname); - } - // Validate the subsort, if present. - if ($subsort) { - $subsorts = $column->is_sortable(); - if (!is_array($subsorts) || !isset($subsorts[$subsort])) { - throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $sort); - } - } - return array($colname, $subsort); - } - - protected function init_sort_from_params() { - $this->sort = array(); - for ($i = 1; $i <= self::MAX_SORTS; $i++) { - if (!$sort = optional_param('qbs' . $i, '', PARAM_TEXT)) { - break; - } - // Work out the appropriate order. - $order = 1; - if ($sort[0] == '-') { - $order = -1; - $sort = substr($sort, 1); - if (!$sort) { - break; - } - } - // Deal with subsorts. - list($colname) = $this->parse_subsort($sort); - $this->requiredcolumns[$colname] = $this->get_column_type($colname); - $this->sort[$sort] = $order; - } - } - - protected function sort_to_params($sorts) { - $params = array(); - $i = 0; - foreach ($sorts as $sort => $order) { - $i += 1; - if ($order < 0) { - $sort = '-' . $sort; - } - $params['qbs' . $i] = $sort; - } - return $params; - } - - protected function default_sort() { - return array( - 'core_question\bank\question_type_column' => 1, - 'core_question\bank\question_name_idnumber_tags_column-name' => 1 - ); - } - - /** - * @param string $sort a column or column_subsort name. - * @return int the current sort order for this column -1, 0, 1 - */ - public function get_primary_sort_order($sort) { - $order = reset($this->sort); - $primarysort = key($this->sort); - if ($sort == $primarysort) { - return $order; - } else { - return 0; - } - } - - /** - * Get a URL to redisplay the page with a new sort for the question bank. - * - * @param string $sort the column, or column_subsort to sort on. - * @param bool $newsortreverse whether to sort in reverse order. - * @return string The new URL. - */ - public function new_sort_url($sort, $newsortreverse) { - if ($newsortreverse) { - $order = -1; - } else { - $order = 1; - } - // Tricky code to add the new sort at the start, removing it from where it was before, if it was present. - $newsort = array_reverse($this->sort); - if (isset($newsort[$sort])) { - unset($newsort[$sort]); - } - $newsort[$sort] = $order; - $newsort = array_reverse($newsort); - if (count($newsort) > self::MAX_SORTS) { - $newsort = array_slice($newsort, 0, self::MAX_SORTS, true); - } - return $this->baseurl->out(true, $this->sort_to_params($newsort)); - } - - /** - * Create the SQL query to retrieve the indicated questions - * - * @param \stdClass $category no longer used. - * @param bool $recurse no longer used. - * @param bool $showhidden no longer used. - * @deprecated since Moodle 2.7 MDL-40313. - * @see build_query() - * @see \core_question\bank\search\condition - * @todo MDL-41978 This will be deleted in Moodle 2.8 - */ - protected function build_query_sql($category, $recurse, $showhidden) { - debugging('build_query_sql() is deprecated, please use \core_question\bank\view::build_query() and ' . - '\core_question\bank\search\condition classes instead.', DEBUG_DEVELOPER); - self::build_query(); - } - - /** - * Create the SQL query to retrieve the indicated questions, based on - * \core_question\bank\search\condition filters. - */ - protected function build_query() { - // Get the required tables and fields. - $joins = array(); - $fields = array('q.hidden', 'q.category'); - foreach ($this->requiredcolumns as $column) { - $extrajoins = $column->get_extra_joins(); - foreach ($extrajoins as $prefix => $join) { - if (isset($joins[$prefix]) && $joins[$prefix] != $join) { - throw new \coding_exception('Join ' . $join . ' conflicts with previous join ' . $joins[$prefix]); - } - $joins[$prefix] = $join; - } - $fields = array_merge($fields, $column->get_required_fields()); - } - $fields = array_unique($fields); - - // Build the order by clause. - $sorts = 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. - $tests = array('q.parent = 0'); - $this->sqlparams = array(); - foreach ($this->searchconditions as $searchcondition) { - if ($searchcondition->where()) { - $tests[] = '((' . $searchcondition->where() .'))'; - } - if ($searchcondition->params()) { - $this->sqlparams = array_merge($this->sqlparams, $searchcondition->params()); - } - } - // Build the SQL. - $sql = ' FROM {question} q ' . implode(' ', $joins); - $sql .= ' WHERE ' . implode(' AND ', $tests); - $this->countsql = 'SELECT count(1)' . $sql; - $this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts); - } - - protected function get_question_count() { - global $DB; - return $DB->count_records_sql($this->countsql, $this->sqlparams); - } - - /** - * Load the questions we need to display. - * - * @param int $page page to display. - * @param int $perpage number of questions per page. - * @return \moodle_recordset questionid => data about each question. - */ - protected function load_page_questions($page, $perpage) { - global $DB; - $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, $page * $perpage, $perpage); - if (empty($questions)) { - $questions->close(); - // No questions on this page. Reset to page 0. - $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, 0, $perpage); - } - return $questions; - } - - public function base_url() { - return $this->baseurl; - } - - /** - * Get the URL for editing a question as a {@link \moodle_url}. - * - * @param int $questionid the question id. - * @return \moodle_url the URL, HTML-escaped. - */ - public function edit_question_moodle_url($questionid) { - return new \moodle_url($this->editquestionurl, ['id' => $questionid]); - } - - /** - * Get the URL for editing a question as a HTML-escaped string. - * - * @param int $questionid the question id. - * @return string the URL, HTML-escaped. - */ - public function edit_question_url($questionid) { - return $this->edit_question_moodle_url($questionid)->out(); - } - - /** - * Get the URL for duplicating a question as a {@link \moodle_url}. - * - * @param int $questionid the question id. - * @return \moodle_url the URL. - */ - public function copy_question_moodle_url($questionid) { - return new \moodle_url($this->editquestionurl, ['id' => $questionid, 'makecopy' => 1]); - } - - /** - * Get the URL for duplicating a given question. - * @param int $questionid the question id. - * @return string the URL, HTML-escaped. - */ - public function copy_question_url($questionid) { - return $this->copy_question_moodle_url($questionid)->out(); - } - - /** - * Get the context we are displaying the question bank for. - * @return \context context object. - */ - public function get_most_specific_context() { - return $this->contexts->lowest(); - } - - /** - * Get the URL to preview a question. - * @param \stdClass $questiondata the data defining the question. - * @return \moodle_url the URL. - */ - public function preview_question_url($questiondata) { - return question_preview_url($questiondata->id, null, null, null, null, - $this->get_most_specific_context()); - } - - /** - * Shows the question bank editing 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 - * - * @param string $tabname question bank edit tab name, for permission checking. - * @param int $page the page number to show. - * @param int $perpage the number of questions per page to show. - * @param string $cat 'categoryid,contextid'. - * @param int $recurse Whether to include subcategories. - * @param bool $showhidden whether deleted questions should be displayed. - * @param bool $showquestiontext whether the text of each question should be shown in the list. Deprecated. - * @param array $tagids current list of selected tags. - */ - public function display($tabname, $page, $perpage, $cat, - $recurse, $showhidden, $showquestiontext, $tagids = []) { - global $PAGE, $CFG; - - if ($this->process_actions_needing_ui()) { - return; - } - $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); - list(, $contextid) = explode(',', $cat); - $catcontext = \context::instance_by_id($contextid); - $thiscontext = $this->get_most_specific_context(); - // Category selection form. - $this->display_question_bank_header(); - - // Display tag filter if usetags setting is enabled. - if ($CFG->usetags) { - array_unshift($this->searchconditions, - new \core_question\bank\search\tag_condition([$catcontext, $thiscontext], $tagids)); - $PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']); - } - - array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden)); - array_unshift($this->searchconditions, new \core_question\bank\search\category_condition( - $cat, $recurse, $editcontexts, $this->baseurl, $this->course)); - $this->display_options_form($showquestiontext); - - // Continues with list of questions. - $this->display_question_list($editcontexts, - $this->baseurl, $cat, $this->cm, - null, $page, $perpage, $showhidden, $showquestiontext, - $this->contexts->having_cap('moodle/question:add')); - - } - - protected function print_choose_category_message($categoryandcontext) { - echo "

"; - print_string('selectcategoryabove', 'question'); - echo "

"; - } - - protected function get_current_category($categoryandcontext) { - global $DB, $OUTPUT; - list($categoryid, $contextid) = explode(',', $categoryandcontext); - if (!$categoryid) { - $this->print_choose_category_message($categoryandcontext); - return false; - } - - if (!$category = $DB->get_record('question_categories', - array('id' => $categoryid, 'contextid' => $contextid))) { - echo $OUTPUT->box_start('generalbox questionbank'); - echo $OUTPUT->notification('Category not found!'); - echo $OUTPUT->box_end(); - return false; - } - - return $category; - } - - /** - * prints category information - * @param \stdClass $category the category row from the database. - * @deprecated since Moodle 2.7 MDL-40313. - * @see \core_question\bank\search\condition - * @todo MDL-41978 This will be deleted in Moodle 2.8 - */ - protected function print_category_info($category) { - $formatoptions = new \stdClass(); - $formatoptions->noclean = true; - $formatoptions->overflowdiv = true; - echo '
'; - echo format_text($category->info, $category->infoformat, $formatoptions, $this->course->id); - echo "
\n"; - } - - /** - * Prints a form to choose categories - * @deprecated since Moodle 2.7 MDL-40313. - * @see \core_question\bank\search\condition - * @todo MDL-41978 This will be deleted in Moodle 2.8 - */ - protected function display_category_form($contexts, $pageurl, $current) { - global $OUTPUT; - - debugging('display_category_form() is deprecated, please use ' . - '\core_question\bank\search\condition instead.', DEBUG_DEVELOPER); - // Get all the existing categories now. - echo '
'; - $catmenu = question_category_options($contexts, false, 0, true); - - $select = new \single_select($this->baseurl, 'category', $catmenu, $current, null, 'catmenu'); - $select->set_label(get_string('selectacategory', 'question')); - echo $OUTPUT->render($select); - echo "
\n"; - } - - /** - * Display the options form. - * @param bool $recurse no longer used. - * @param bool $showhidden no longer used. - * @param bool $showquestiontext whether to show the question text. - * @deprecated since Moodle 2.7 MDL-40313. - * @see display_options_form - * @todo MDL-41978 This will be deleted in Moodle 2.8 - * @see \core_question\bank\search\condition - */ - protected function display_options($recurse, $showhidden, $showquestiontext) { - debugging('display_options() is deprecated, please use display_options_form instead.', DEBUG_DEVELOPER); - $this->display_options_form($showquestiontext); - } - - /** - * Print a single option checkbox. - * @deprecated since Moodle 2.7 MDL-40313. - * @see \core_question\bank\search\condition - * @see html_writer::checkbox - * @todo MDL-41978 This will be deleted in Moodle 2.8 - */ - protected function display_category_form_checkbox($name, $value, $label) { - debugging('display_category_form_checkbox() is deprecated, ' . - 'please use \core_question\bank\search\condition instead.', DEBUG_DEVELOPER); - echo '
'; - echo ''; - echo ''; - echo "
\n"; - } - - /** - * Display the form with options for which questions are displayed and how they are displayed. - * @param bool $showquestiontext Display the text of the question within the list. - * @param string $scriptpath path to the script displaying this page. - * @param bool $showtextoption whether to include the 'Show question text' checkbox. - */ - protected function display_options_form($showquestiontext, $scriptpath = '/question/edit.php', - $showtextoption = true) { - global $PAGE; - - echo \html_writer::start_tag('form', array('method' => 'get', - 'action' => new \moodle_url($scriptpath), 'id' => 'displayoptions')); - echo \html_writer::start_div(); - - $excludes = array('recurse', 'showhidden', 'qbshowtext'); - // If the URL contains any tags then we need to prevent them - // being added to the form as hidden elements because the tags - // are managed separately. - if ($this->baseurl->param('qtagids[0]')) { - $index = 0; - while ($this->baseurl->param("qtagids[{$index}]")) { - $excludes[] = "qtagids[{$index}]"; - $index++; - } - } - echo \html_writer::input_hidden_params($this->baseurl, $excludes); - - foreach ($this->searchconditions as $searchcondition) { - echo $searchcondition->display_options(); - } - if ($showtextoption) { - $this->display_showtext_checkbox($showquestiontext); - } - $this->display_advanced_search_form(); - $go = \html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'))); - echo \html_writer::tag('noscript', \html_writer::div($go), array('class' => 'inline')); - echo \html_writer::end_div(); - echo \html_writer::end_tag('form'); - $PAGE->requires->yui_module('moodle-question-searchform', 'M.question.searchform.init'); - } - - /** - * Print the "advanced" UI elements for the form to select which questions. Hidden by default. - */ - protected function display_advanced_search_form() { - print_collapsible_region_start('', 'advancedsearch', get_string('advancedsearchoptions', 'question'), - 'question_bank_advanced_search'); - foreach ($this->searchconditions as $searchcondition) { - echo $searchcondition->display_options_adv(); - } - print_collapsible_region_end(); - } - - /** - * Display the checkbox UI for toggling the display of the question text in the list. - * @param bool $showquestiontext the current or default value for whether to display the text. - */ - protected function display_showtext_checkbox($showquestiontext) { - echo '
'; - echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'qbshowtext', - 'value' => 0, 'id' => 'qbshowtext_off')); - echo \html_writer::checkbox('qbshowtext', '1', $showquestiontext, ' ' . get_string('showquestiontext', 'question'), - array('id' => 'qbshowtext_on', 'class' => 'searchoptions mr-1')); - echo "
\n"; - } - - /** - * Display the header element for the question bank. - */ - protected function display_question_bank_header() { - global $OUTPUT; - echo $OUTPUT->heading(get_string('questionbank', 'question'), 2); - } - - protected function create_new_question_form($category, $canadd) { - echo '
'; - if ($canadd) { - create_new_question_button($category->id, $this->editquestionurl->params(), - get_string('createnewquestion', 'question')); - } else { - print_string('nopermissionadd', 'question'); - } - echo '
'; - } - - /** - * Prints the table of questions in a category with interactions - * - * @param array $contexts Not used! - * @param \moodle_url $pageurl The URL to reload this page. - * @param string $categoryandcontext 'categoryID,contextID'. - * @param \stdClass $cm Not used! - * @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 bool $showhidden Not used! This is now controlled in a different way. - * @param bool $showquestiontext Not used! This is now controlled in a different way. - * @param array $addcontexts contexts where the user is allowed to add new questions. - */ - protected function display_question_list($contexts, $pageurl, $categoryandcontext, - $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false, - $showquestiontext = false, $addcontexts = array()) { - global $OUTPUT; - - // This function can be moderately slow with large question counts and may time out. - // We probably do not want to raise it to unlimited, so randomly picking 5 minutes. - // Note: We do not call this in the loop because quiz ob_ captures this function (see raise() PHP doc). - \core_php_time_limit::raise(300); - - $category = $this->get_current_category($categoryandcontext); - - list($categoryid, $contextid) = explode(',', $categoryandcontext); - $catcontext = \context::instance_by_id($contextid); - - $canadd = has_capability('moodle/question:add', $catcontext); - - $this->create_new_question_form($category, $canadd); - - $this->build_query(); - $totalnumber = $this->get_question_count(); - if ($totalnumber == 0) { - return; - } - $questionsrs = $this->load_page_questions($page, $perpage); - $questions = []; - foreach ($questionsrs as $question) { - $questions[$question->id] = $question; - } - $questionsrs->close(); - foreach ($this->requiredcolumns as $name => $column) { - $column->load_additional_data($questions); - } - - echo '
'; - $pageingurl = new \moodle_url('edit.php', $pageurl->params()); - $pagingbar = new \paging_bar($totalnumber, $page, $perpage, $pageingurl); - $pagingbar->pagevar = 'qpage'; - echo $OUTPUT->render($pagingbar); - echo '
'; - - echo '
'; - echo '
'; - echo ''; - echo \html_writer::input_hidden_params($this->baseurl); - - echo '
'; - $this->start_table(); - $rowcount = 0; - foreach ($questions as $question) { - $this->print_table_row($question, $rowcount); - $rowcount += 1; - } - $this->end_table(); - echo "
\n"; - - echo '
'; - echo $OUTPUT->render($pagingbar); - if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) { - if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) { - $url = new \moodle_url('edit.php', array_merge($pageurl->params(), - array('qpage' => 0, 'qperpage' => MAXIMUM_QUESTIONS_PER_PAGE))); - if ($totalnumber > MAXIMUM_QUESTIONS_PER_PAGE) { - $showall = ''.get_string('showperpage', 'moodle', MAXIMUM_QUESTIONS_PER_PAGE).''; - } else { - $showall = ''.get_string('showall', 'moodle', $totalnumber).''; - } - } else { - $url = new \moodle_url('edit.php', array_merge($pageurl->params(), - array('qperpage' => DEFAULT_QUESTIONS_PER_PAGE))); - $showall = ''.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).''; - } - echo "
{$showall}
"; - } - echo '
'; - - $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts); - - echo '
'; - echo "
\n"; - } - - /** - * Display the controls at the bottom of the list of questions. - * @param int $totalnumber Total number of questions that might be shown (if it was not for paging). - * @param bool $recurse Whether to include subcategories. - * @param \stdClass $category The question_category row from the database. - * @param \context $catcontext The context of the category being displayed. - * @param array $addcontexts contexts where the user is allowed to add new questions. - */ - protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) { - $caneditall = has_capability('moodle/question:editall', $catcontext); - $canuseall = has_capability('moodle/question:useall', $catcontext); - $canmoveall = has_capability('moodle/question:moveall', $catcontext); - - echo '
'; - if ($caneditall || $canmoveall || $canuseall) { - echo ' '.get_string('withselected', 'question').':
'; - - // Print delete and move selected question. - if ($caneditall) { - echo \html_writer::empty_tag('input', [ - 'type' => 'submit', - 'class' => 'btn btn-secondary mr-1', - 'name' => 'deleteselected', - 'value' => get_string('delete'), - 'data-action' => 'toggle', - 'data-togglegroup' => 'qbank', - 'data-toggle' => 'action', - 'disabled' => true, - ]); - } - - if ($canmoveall && count($addcontexts)) { - echo \html_writer::empty_tag('input', [ - 'type' => 'submit', - 'class' => 'btn btn-secondary mr-1', - 'name' => 'move', - 'value' => get_string('moveto', 'question'), - 'data-action' => 'toggle', - 'data-togglegroup' => 'qbank', - 'data-toggle' => 'action', - 'disabled' => true, - ]); - question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}"); - } - } - echo "
\n"; - } - - protected function start_table() { - echo '' . "\n"; - echo "\n"; - $this->print_table_headers(); - echo "\n"; - echo "\n"; - } - - protected function end_table() { - echo "\n"; - echo "
\n"; - } - - protected function print_table_headers() { - echo "\n"; - foreach ($this->visiblecolumns as $column) { - $column->display_header(); - } - echo "\n"; - } - - protected function get_row_classes($question, $rowcount) { - $classes = array(); - if ($question->hidden) { - $classes[] = 'dimmed_text'; - } - if ($question->id == $this->lastchangedid) { - $classes[] = 'highlight text-dark'; - } - $classes[] = 'r' . ($rowcount % 2); - return $classes; - } - - protected function print_table_row($question, $rowcount) { - $rowclasses = implode(' ', $this->get_row_classes($question, $rowcount)); - if ($rowclasses) { - echo '' . "\n"; - } else { - echo "\n"; - } - foreach ($this->visiblecolumns as $column) { - $column->display($question, $rowclasses); - } - echo "\n"; - foreach ($this->extrarows as $row) { - $row->display($question, $rowclasses); - } - } - - public function process_actions() { - global $DB; - // Now, check for commands on this page and modify variables as necessary. - if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { - // Move selected questions to new category. - $category = required_param('category', PARAM_SEQUENCE); - list($tocategoryid, $contextid) = explode(',', $category); - if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) { - print_error('cannotfindcate', 'question'); - } - $tocontext = \context::instance_by_id($contextid); - require_capability('moodle/question:add', $tocontext); - $rawdata = (array) data_submitted(); - $questionids = array(); - foreach ($rawdata as $key => $value) { // Parse input for question ids. - if (preg_match('!^q([0-9]+)$!', $key, $matches)) { - $key = $matches[1]; - $questionids[] = $key; - } - } - if ($questionids) { - list($usql, $params) = $DB->get_in_or_equal($questionids); - $questions = $DB->get_records_sql(" - SELECT q.*, c.contextid - FROM {question} q - JOIN {question_categories} c ON c.id = q.category - WHERE q.id {$usql}", $params); - foreach ($questions as $question) { - question_require_capability_on($question, 'move'); - } - question_move_questions_to_category($questionids, $tocategory->id); - redirect($this->baseurl->out(false, - array('category' => "{$tocategoryid},{$contextid}"))); - } - } - - if (optional_param('deleteselected', false, PARAM_BOOL)) { // Delete selected questions from the category. - // If teacher has already confirmed the action. - if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { - $deleteselected = required_param('deleteselected', PARAM_RAW); - if ($confirm == md5($deleteselected)) { - if ($questionlist = explode(',', $deleteselected)) { - // For each question either hide it if it is in use or delete it. - foreach ($questionlist as $questionid) { - $questionid = (int)$questionid; - question_require_capability_on($questionid, 'edit'); - if (questions_in_use(array($questionid))) { - $DB->set_field('question', 'hidden', 1, array('id' => $questionid)); - } else { - question_delete_question($questionid); - } - } - } - redirect($this->baseurl); - } else { - print_error('invalidconfirm', 'question'); - } - } - } - - // Unhide a question. - if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) { - question_require_capability_on($unhide, 'edit'); - $DB->set_field('question', 'hidden', 0, array('id' => $unhide)); - - // Purge these questions from the cache. - \question_bank::notify_question_edited($unhide); - - redirect($this->baseurl); - } - } - - public function process_actions_needing_ui() { - global $DB, $OUTPUT; - if (optional_param('deleteselected', false, PARAM_BOOL)) { - // Make a list of all the questions that are selected. - $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. - $questionlist = ''; // comma separated list of ids of questions to be deleted - $questionnames = ''; // string with names of questions separated by
with - // an asterix in front of those that are in use - $inuse = false; // set to true if at least one of the questions is in use - foreach ($rawquestions as $key => $value) { // Parse input for question ids. - if (preg_match('!^q([0-9]+)$!', $key, $matches)) { - $key = $matches[1]; - $questionlist .= $key.','; - question_require_capability_on((int)$key, 'edit'); - if (questions_in_use(array($key))) { - $questionnames .= '* '; - $inuse = true; - } - $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . '
'; - } - } - if (!$questionlist) { // No questions were selected. - redirect($this->baseurl); - } - $questionlist = rtrim($questionlist, ','); - - // Add an explanation about questions in use. - if ($inuse) { - $questionnames .= '
'.get_string('questionsinuse', 'question'); - } - $baseurl = new \moodle_url('edit.php', $this->baseurl->params()); - $deleteurl = new \moodle_url($baseurl, array('deleteselected' => $questionlist, 'confirm' => md5($questionlist), - 'sesskey' => sesskey())); - - $continue = new \single_button($deleteurl, get_string('delete'), 'post'); - echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $continue, $baseurl); - - return true; - } - - return false; - } - - /** - * Add another search control to this view. - * @param condition $searchcondition the condition to add. - */ - public function add_searchcondition($searchcondition) { - $this->searchconditions[] = $searchcondition; - } -} diff --git a/question/classes/external.php b/question/classes/external.php index 3a5661d1231..838afb87a41 100644 --- a/question/classes/external.php +++ b/question/classes/external.php @@ -170,7 +170,6 @@ class core_question_external extends external_api { } require_once($CFG->libdir . '/questionlib.php'); - require_once($CFG->dirroot . '/question/type/tags_form.php'); $cantag = question_has_capability_on($question, 'tag'); $questioncontext = \context::instance_by_id($question->contextid); @@ -182,7 +181,7 @@ class core_question_external extends external_api { 'contexts' => $contexts->all() ]; - $mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data); + $mform = new \qbank_tagquestion\form\tags_form(null, $formoptions, 'post', '', null, $cantag, $data); if ($validateddata = $mform->get_data()) { if ($cantag) { diff --git a/question/classes/local/bank/edit_menu_column.php b/question/classes/local/bank/edit_menu_column.php index ea061d70755..3810733c6cf 100644 --- a/question/classes/local/bank/edit_menu_column.php +++ b/question/classes/local/bank/edit_menu_column.php @@ -51,12 +51,11 @@ class edit_menu_column extends column_base { * * @param column_base[] $allcolumns a set of columns. * @return column_base[] the non-action columns from the set. - * @todo MDL-72004 changes for class renaming. */ public function claim_menuable_columns($allcolumns): array { $remainingcolumns = []; foreach ($allcolumns as $key => $column) { - if ($column instanceof menuable_action || $column instanceof \core_question\bank\menuable_action) { + if ($column instanceof menuable_action) { $this->actions[$key] = $column; } else { $remainingcolumns[$key] = $column; diff --git a/question/classes/local/bank/view.php b/question/classes/local/bank/view.php index 8b89333c626..c89c21532cb 100644 --- a/question/classes/local/bank/view.php +++ b/question/classes/local/bank/view.php @@ -198,7 +198,6 @@ class view { * Get the list of qbank plugins with available objects for features. * * @return array - * @todo MDL-72004 changes for class renaming. */ protected function get_question_bank_plugins(): array { $questionbankclasscolumns = []; @@ -223,16 +222,12 @@ class view { foreach ($corequestionbankcolumns as $fullname) { $shortname = $fullname; - if (!class_exists($fullname)) { - if (class_exists('core_question\\local\\bank\\' . $fullname)) { - $fullname = 'core_question\\local\\bank\\' . $fullname; - } else if (class_exists('core_question\\bank\\' . $fullname)) { - $fullname = 'core_question\\bank\\' . $fullname; - } else { - throw new \coding_exception("No such class exists: $fullname"); - } + if (class_exists('core_question\\local\\bank\\' . $fullname)) { + $fullname = 'core_question\\local\\bank\\' . $fullname; + $questionbankclasscolumns[$shortname] = new $fullname($this); + } else { + $questionbankclasscolumns[$shortname] = ''; } - $questionbankclasscolumns[$shortname] = new $fullname($this); } $plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php'); foreach ($plugins as $componentname => $plugin) { @@ -271,6 +266,13 @@ class view { $questionbankclasscolumns[$key] = $newpluginclasscolumn; } + // Mitigate the error in case of any regression. + foreach ($questionbankclasscolumns as $shortname => $questionbankclasscolumn) { + if (empty($questionbankclasscolumn)){ + unset($questionbankclasscolumns[$shortname]); + } + } + return $questionbankclasscolumns; } @@ -278,30 +280,15 @@ class view { * Loads all the available columns. * * @return array - * @todo MDL-72004 changes for class renaming. */ protected function wanted_columns(): array { - global $CFG; $this->requiredcolumns = []; - - if (empty($CFG->questionbankcolumns)) { - $questionbankcolumns = $this->get_question_bank_plugins(); - foreach ($questionbankcolumns as $classobject) { - $this->requiredcolumns[get_class($classobject)] = $classobject; - } - } else { - // Config overrides the array, but uses the deprecated classes. - $questionbankcolumns = explode(',', $CFG->questionbankcolumns); - foreach ($questionbankcolumns as $fullname) { - if (! class_exists($fullname)) { - if (class_exists('core_question\\bank\\' . $fullname)) { - $fullname = 'core_question\\bank\\' . $fullname; - } else { - throw new \coding_exception("No such class exists: $fullname"); - } - } - $this->requiredcolumns[$fullname] = new $fullname($this); + $questionbankcolumns = $this->get_question_bank_plugins(); + foreach ($questionbankcolumns as $classobject) { + if (empty($classobject)) { + continue; } + $this->requiredcolumns[get_class($classobject)] = $classobject; } return $this->requiredcolumns; @@ -333,12 +320,11 @@ class view { * * @param array $wanted Collection of column names * @param string $heading The name of column that is set as heading - * @todo MDL-72004 changes for class renaming. */ protected function init_columns($wanted, $heading = ''): void { // If we are using the edit menu column, allow it to absorb all the actions. foreach ($wanted as $column) { - if ($column instanceof edit_menu_column || $column instanceof \core_question\bank\edit_menu_column) { + if ($column instanceof edit_menu_column) { $wanted = $column->claim_menuable_columns($wanted); break; } @@ -479,20 +465,15 @@ class view { /** * Default sort for question data. * @return int[] - * @todo MDL-72004 changes for class renaming. */ protected function default_sort(): array { $defaultsort = []; if (class_exists('\\qbank_viewquestiontype\\question_type_column')) { $sort = 'qbank_viewquestiontype\question_type_column'; - } else { - $sort = 'core_question\bank\question_type_column'; } $defaultsort[$sort] = 1; if (class_exists('\\qbank_viewquestionname\\question_name_idnumber_tags_column')) { $sort = 'qbank_viewquestionname\question_name_idnumber_tags_column'; - } else { - $sort = 'core_question\bank\question_name_idnumber_tags_column'; } $defaultsort[$sort . '-name'] = 1; @@ -544,7 +525,6 @@ class view { /** * Create the SQL query to retrieve the indicated questions, based on * \core_question\bank\search\condition filters. - * @todo MDL-72004 changes for class renaming and default sort. */ protected function build_query(): void { // Get the required tables and fields. @@ -878,8 +858,6 @@ class view { if (\core\plugininfo\qbank::is_plugin_enabled('qbank_viewquestiontext')) { echo $PAGE->get_renderer('core_question', 'bank')->render_showtext_checkbox($displaydata); } - } else { - echo $PAGE->get_renderer('core_question', 'bank')->render_showtext_checkbox($displaydata); } } @@ -1116,6 +1094,7 @@ class view { * * @deprecated since Moodle 4.0 * @see print_table() + * @todo Final deprecation of this function in moodle 4.4 */ protected function start_table() { debugging('Function start_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER); @@ -1131,6 +1110,7 @@ class view { * * @deprecated since Moodle 4.0 * @see print_table() + * @todo Final deprecation of this function in moodle 4.4 */ protected function end_table() { debugging('Function end_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER); diff --git a/question/classes/output/qbank_chooser.php b/question/classes/output/qbank_chooser.php deleted file mode 100644 index 8f278291454..00000000000 --- a/question/classes/output/qbank_chooser.php +++ /dev/null @@ -1,108 +0,0 @@ -. - -/** - * The qbank_chooser renderable. - * - * @package core_question - * @copyright 2016 Frédéric Massart - FMCorz.net - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\output; -defined('MOODLE_INTERNAL') || die(); - -use context_course; -use core\output\chooser_section; -use lang_string; -use moodle_url; -use question_bank; - - -/** - * The qbank_chooser renderable class. - * - * @package core_question - * @copyright 2016 Frédéric Massart - FMCorz.net - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 - * @see \qbank_editquestion\qbank_chooser - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class qbank_chooser extends \core\output\chooser { - - /** - * Constructor. - * - * @param array $real The real question types. - * @param array $fake The fake question types. - * @param stdClass $course The course. - * @param array $hiddenparams Hidden parameters. - * @param context $context The relevant context. - */ - public function __construct($real, $fake, $course, $hiddenparams, $context) { - $sections = []; - $sections[] = new chooser_section('questions', new lang_string('questions', 'question'), - array_map(function($qtype) use ($context) { - return new qbank_chooser_item($qtype, $context); - }, $real)); - - if (!empty($fake)) { - $sections[] = new chooser_section('other', new lang_string('other'), - array_map(function ($qtype) use ($context) { - return new qbank_chooser_item($qtype, $context); - }, $fake)); - } - - parent::__construct(new moodle_url('/question/question.php'), - new lang_string('chooseqtypetoadd', 'question'), $sections, 'qtype'); - - $this->set_instructions(new lang_string('selectaqtypefordescription', 'question')); - - $this->set_method('get'); - $this->add_param('courseid', $course->id); - foreach ($hiddenparams as $k => $v) { - $this->add_param($k, $v); - } - } - - /** - * Get an instance of the question bank chooser. - * - * @param stdClass $course The course. - * @param array $hiddenparams Hidden parameters. - * @param array|null $allowedqtypes Allowed question types. - * @return qbank_chooser - */ - public static function get($course, $hiddenparams, array $allowedqtypes = null) { - $realqtypes = array(); - $fakeqtypes = array(); - - foreach (question_bank::get_creatable_qtypes() as $qtypename => $qtype) { - if ($allowedqtypes && !in_array($qtypename, $allowedqtypes)) { - continue; - } - if ($qtype->is_real_question_type()) { - $realqtypes[] = $qtype; - } else { - $fakeqtypes[] = $qtype; - } - } - - return new static($realqtypes, $fakeqtypes, $course, $hiddenparams, context_course::instance($course->id)); - } - -} diff --git a/question/classes/output/qbank_chooser_item.php b/question/classes/output/qbank_chooser_item.php deleted file mode 100644 index 137025e7f56..00000000000 --- a/question/classes/output/qbank_chooser_item.php +++ /dev/null @@ -1,59 +0,0 @@ -. - -/** - * The qbank_chooser_item renderable. - * - * @package core_question - * @copyright 2016 Frédéric Massart - FMCorz.net - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\output; -defined('MOODLE_INTERNAL') || die(); - -use lang_string; -use pix_icon; - - -/** - * The qbank_chooser_item renderable class. - * - * @package core_question - * @copyright 2016 Frédéric Massart - FMCorz.net - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 - * @see \qbank_editquestion\qbank_chooser_item - * @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin - */ -class qbank_chooser_item extends \core\output\chooser_item { - - /** - * Constructor. - * - * @param object $qtype The question type. - * @param context $context The relevant context. - */ - public function __construct($qtype, $context) { - $icon = new pix_icon('icon', $qtype->local_name(), $qtype->plugin_name(), [ - 'class' => 'icon', - 'title' => $qtype->local_name() - ]); - $help = new lang_string('pluginnamesummary', $qtype->plugin_name()); - parent::__construct($qtype->plugin_name(), $qtype->menu_name(), $qtype->name(), $icon, $help, $context); - } - -} diff --git a/question/editlib.php b/question/editlib.php index 64bfa604d30..871d7b92b4f 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -108,7 +108,7 @@ function question_is_only_child_of_top_category_in_context($categoryid) { has been deprecated and moved to qbank_managecategories plugin, Please use qbank_managecategories\helper::question_is_only_child_of_top_category_in_context() instead.', DEBUG_DEVELOPER); - return \qbank_managecategories\helper::question_is_only_child_of_top_category_in_contextt($categoryid); + return \qbank_managecategories\helper::question_is_only_child_of_top_category_in_context($categoryid); } /** @@ -140,151 +140,6 @@ function question_can_delete_cat($todelete) { \qbank_managecategories\helper::question_can_delete_cat($todelete); } - -/** - * Base class for representing a column in a {@see question_bank_view}. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\local\bank\column_base', 'question_bank_column_base', true); - -/** - * A column with a checkbox for each question with name q{questionid}. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\local\bank\checkbox_column', 'question_bank_checkbox_column', true); - -/** - * A column type for the name of the question type. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_viewquestiontype\question_type_column', 'question_bank_question_type_column', true); - - -/** - * A column type for the name of the question name. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_viewquestionname\viewquestionname_column_helper', 'question_bank_question_name_column', true); - - -/** - * A column type for the name of the question creator. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_viewcreator\creator_name_column', 'question_bank_creator_name_column', true); - - -/** - * A column type for the name of the question last modifier. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_viewcreator\modifier_name_column', 'question_bank_modifier_name_column', true); - - -/** - * A base class for actions that are an icon that lets you manipulate the question in some way. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\local\bank\action_column_base', 'question_bank_action_column_base', true); - - -/** - * Base class for question bank columns that just contain an action icon. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_editquestion\edit_action_column', 'question_bank_edit_action_column', true); - -/** - * Question bank column for the duplicate action icon. - * - * @copyright 2013 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_editquestion\copy_action_column', 'question_bank_copy_action_column', true); - -/** - * Question bank columns for the preview action icon. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_previewquestion\preview_action_column', 'question_bank_preview_action_column', true); - - -/** - * action to delete (or hide) a question, or restore a previously hidden question. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\bank\delete_action_column', 'question_bank_delete_action_column', true); - -/** - * Base class for 'columns' that are actually displayed as a row following the main question row. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\local\bank\row_base', 'question_bank_row_base', true); - -/** - * A column type for the name of the question name. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('qbank_viewquestiontext\question_text_row', 'question_bank_question_text_row', true); - -/** - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 2.7 MDL-40457 - * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. - */ -class_alias('core_question\local\bank\view', 'question_bank_view', true); - /** * Common setup for all pages for editing questions. * @param string $baseurl the name of the script calling this funciton. For examle 'qusetion/edit.php'. @@ -322,7 +177,7 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $unused = $params['qperpage'] = optional_param('qperpage', null, PARAM_INT); // Question table sorting options. - for ($i = 1; $i <= question_bank_view::MAX_SORTS; $i++) { + for ($i = 1; $i <= core_question\local\bank\view::MAX_SORTS; $i++) { $param = 'qbs' . $i; if ($sort = optional_param($param, '', PARAM_TEXT)) { $params[$param] = $sort; @@ -371,7 +226,7 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $unused = * 'qbs1' => PARAM_TEXT, * 'qbs2' => PARAM_TEXT, * 'qbs3' => PARAM_TEXT, - * ... and more qbs keys up to question_bank_view::MAX_SORTS ... + * ... and more qbs keys up to core_question\local\bank\view::MAX_SORTS ... * ]; * * @param string $edittab Code for this edit tab @@ -481,7 +336,7 @@ function question_build_edit_resources($edittab, $baseurl, $params) { // This need to occur after the override_active_url call above because // these values change on the page request causing the URLs to mismatch // when trying to work out the active node. - for ($i = 1; $i <= question_bank_view::MAX_SORTS; $i++) { + for ($i = 1; $i <= core_question\local\bank\view::MAX_SORTS; $i++) { $param = 'qbs' . $i; if (isset($params[$param])) { $value = clean_param($params[$param], PARAM_TEXT); @@ -637,12 +492,14 @@ function require_login_in_context($contextorid = null){ * those qtypes will be shown. Example value array('description', 'multichoice'). * @deprecated since Moodle 4.0 * @see \qbank_editquestion\editquestion_helper::print_choose_qtype_to_add_form() - * @todo MDL-72004 deprecate the function and add debugging message. + * @todo Final deprecation of this class in moodle 4.4 MDL-72438 */ function print_choose_qtype_to_add_form($hiddenparams, array $allowedqtypes = null, $enablejs = true) { + debugging('Function print_choose_qtype_to_add_form() is deprecated, + please use \qbank_editquestion\editquestion_helper::print_choose_qtype_to_add_form() instead.', DEBUG_DEVELOPER); global $CFG, $PAGE, $OUTPUT; - $chooser = core_question\output\qbank_chooser::get($PAGE->course, $hiddenparams, $allowedqtypes); + $chooser = \qbank_editquestion\qbank_chooser::get($PAGE->course, $hiddenparams, $allowedqtypes); $renderer = $PAGE->get_renderer('question', 'bank'); return $renderer->render($chooser); @@ -661,6 +518,7 @@ function print_choose_qtype_to_add_form($hiddenparams, array $allowedqtypes = nu * @param bool $disabled if true, the button will be disabled. * @deprecated since Moodle 4.0 * @see \qbank_editquestion\editquestion_helper::create_new_question_button() + * @todo Final deprecation of this class in moodle 4.4 MDL-72438 */ function create_new_question_button($categoryid, $params, $caption, $tooltip = '', $disabled = false) { debugging('Function create_new_question_button() has been deprecated and moved to bank/editquestion, @@ -668,7 +526,7 @@ function create_new_question_button($categoryid, $params, $caption, $tooltip = ' global $CFG, $PAGE, $OUTPUT; static $choiceformprinted = false; $params['category'] = $categoryid; - $url = new moodle_url('/question/addquestion.php', $params); + $url = new moodle_url('/question/bank/editquestion/addquestion.php', $params); echo $OUTPUT->single_button($url, $caption, 'get', array('disabled'=>$disabled, 'title'=>$tooltip)); if (!$choiceformprinted) { @@ -678,5 +536,3 @@ function create_new_question_button($categoryid, $params, $caption, $tooltip = ' $choiceformprinted = true; } } - - diff --git a/question/engine/renderer.php b/question/engine/renderer.php index 9c98fca4bce..be328f8447a 100644 --- a/question/engine/renderer.php +++ b/question/engine/renderer.php @@ -50,13 +50,11 @@ class core_question_renderer extends plugin_renderer_base { * If false, just show the icon. * @deprecated since Moodle 4.0 * @see qbank_previewquestion\output\renderer - * @todo MDL-71679 uncomment the debugging message after implementing the changes in mod_quiz * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function question_preview_link($questionid, context $context, $showlabel) { - // Debugging message will be re-added after implementing the changes in mod_quiz. - // ...debugging('Function question_preview_link() has been deprecated and moved to qbank_previewquestion plugin, - // Please use qbank_previewquestion renderer.', DEBUG_DEVELOPER);. + debugging('Function question_preview_link() has been deprecated and moved to qbank_previewquestion plugin, + Please use qbank_previewquestion renderer.', DEBUG_DEVELOPER); return $this->page->get_renderer('qbank_previewquestion')->question_preview_link( $questionid, $context, $showlabel @@ -351,7 +349,7 @@ class core_question_renderer extends plugin_renderer_base { $params['returnurl'] = $params['returnurl']->out_as_local_url(false); } $params['id'] = $qa->get_question_id(); - $editurl = new moodle_url('/question/question.php', $params); + $editurl = new moodle_url('/question/bank/editquestion/question.php', $params); return html_writer::tag('div', html_writer::link( $editurl, $this->pix_icon('t/edit', get_string('edit'), '', array('class' => 'iconsmall')) . diff --git a/question/export_form.php b/question/export_form.php index b23e459b227..666af01ef01 100644 --- a/question/export_form.php +++ b/question/export_form.php @@ -21,78 +21,7 @@ * @subpackage questionbank * @copyright 2007 Jamie Pratt me@jamiep.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - - -/** - * Form to export questions from the question bank. - * - * @copyright 2007 Jamie Pratt me@jamiep.org - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.0 MDL-71573 * @see qbank_exportquestions\form\export_form + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ -class question_export_form extends moodleform { - - protected function definition() { - debugging('Class question_export_form in \core_question\export_form is deprecated, - please use core_question\bank\exportquestions\export_form instead.', DEBUG_DEVELOPER); - - global $OUTPUT; - - $mform = $this->_form; - - $defaultcategory = $this->_customdata['defaultcategory']; - $contexts = $this->_customdata['contexts']; - - // Choice of format, with help. - $mform->addElement('header', 'fileformat', get_string('fileformat', 'question')); - - $fileformatnames = get_import_export_formats('export'); - $radioarray = array(); - $separators = array(); - foreach ($fileformatnames as $shortname => $fileformatname) { - $radioarray[] = $mform->createElement('radio', 'format', '', $fileformatname, $shortname); - - $separator = ''; - if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) { - $separator .= $OUTPUT->help_icon('pluginname', 'qformat_' . $shortname); - } - $separator .= '
'; - $separators[] = $separator; - } - - $radioarray[] = $mform->createElement('static', 'makelasthelpiconshowup', ''); - $mform->addGroup($radioarray, "formatchoices", '', $separators, false); - $mform->addRule("formatchoices", null, 'required', null, 'client'); - - // Export options. - $mform->addElement('header', 'general', get_string('general', 'form')); - - $mform->addElement('questioncategory', 'category', get_string('exportcategory', 'question'), - array('contexts' => $contexts, 'top' => true)); - $mform->setDefault('category', $defaultcategory); - $mform->addHelpButton('category', 'exportcategory', 'question'); - - $categorygroup = array(); - $categorygroup[] = $mform->createElement('checkbox', 'cattofile', '', get_string('tofilecategory', 'question')); - $categorygroup[] = $mform->createElement('checkbox', 'contexttofile', '', get_string('tofilecontext', 'question')); - $mform->addGroup($categorygroup, 'categorygroup', '', '', false); - $mform->disabledIf('categorygroup', 'cattofile', 'notchecked'); - $mform->setDefault('cattofile', 1); - $mform->setDefault('contexttofile', 1); - - // Set a template for the format select elements - $renderer = $mform->defaultRenderer(); - $template = "{help} {element}\n"; - $renderer->setGroupElementTemplate($template, 'format'); - - // Submit buttons. - $this->add_action_buttons(false, get_string('exportquestions', 'question')); - } -} diff --git a/question/exportone.php b/question/exportone.php deleted file mode 100644 index c2de2bc2ce1..00000000000 --- a/question/exportone.php +++ /dev/null @@ -1,79 +0,0 @@ -. - -/** - * Script to download the export of a single question. - * - * @copyright 2015 the Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once(__DIR__.'/../config.php'); - -require_once($CFG->libdir . '/questionlib.php'); -require_once($CFG->dirroot . '/question/format/xml/format.php'); - -// Get the parameters from the URL. -$questionid = required_param('id', PARAM_INT); -$cmid = optional_param('cmid', 0, PARAM_INT); -$courseid = optional_param('courseid', 0, PARAM_INT); -$urlparams = ['id' => $questionid, 'sesskey' => sesskey()]; - -if ($cmid) { - $cm = get_coursemodule_from_id(null, $cmid); - require_login($cm->course, false, $cm); - $thiscontext = context_module::instance($cmid); - $urlparams['cmid'] = $cmid; -} else if ($courseid) { - require_login($courseid, false); - $thiscontext = context_course::instance($courseid); - $urlparams['courseid'] = $courseid; -} else { - print_error('missingcourseorcmid', 'question'); -} -require_sesskey(); - -// Load the necessary data. -$contexts = new question_edit_contexts($thiscontext); -$questiondata = question_bank::load_question_data($questionid); - -// Check permissions. -question_require_capability_on($questiondata, 'view'); - -// Initialise $PAGE. Nothing is output, so this does not really matter. Just avoids notices. -$nexturl = new moodle_url('/question/type/stack/questiontestrun.php', $urlparams); -$PAGE->set_url('/question/exportone.php', $urlparams); -$PAGE->set_heading($COURSE->fullname); -$PAGE->set_pagelayout('admin'); - -// Set up the export format. -$qformat = new qformat_xml(); -$filename = question_default_export_filename($COURSE, $questiondata) . - $qformat->export_file_extension(); -$qformat->setContexts($contexts->having_one_edit_tab_cap('export')); -$qformat->setCourse($COURSE); -$qformat->setQuestions([$questiondata]); -$qformat->setCattofile(false); -$qformat->setContexttofile(false); - -// Do the export. -if (!$qformat->exportpreprocess()) { - send_file_not_found(); -} -if (!$content = $qformat->exportprocess(true)) { - send_file_not_found(); -} -send_file($content, $filename, 0, 0, true, true, $qformat->mime_type()); diff --git a/question/import.php b/question/import.php deleted file mode 100644 index 567fb741353..00000000000 --- a/question/import.php +++ /dev/null @@ -1,149 +0,0 @@ -. - -/** - * Defines the import questions form. - * - * @package moodlecore - * @subpackage questionbank - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(__DIR__ . '/../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/question/import_form.php'); -require_once($CFG->dirroot . '/question/format.php'); - -list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = - question_edit_setup('import', '/question/import.php'); - -// get display strings -$txt = new stdClass(); -$txt->importerror = get_string('importerror', 'question'); -$txt->importquestions = get_string('importquestions', 'question'); - -list($catid, $catcontext) = explode(',', $pagevars['cat']); -if (!$category = $DB->get_record("question_categories", array('id' => $catid))) { - print_error('nocategory', 'question'); -} - -$categorycontext = context::instance_by_id($category->contextid); -$category->context = $categorycontext; -//this page can be called without courseid or cmid in which case -//we get the context from the category object. -if ($contexts === null) { // need to get the course from the chosen category - $contexts = new question_edit_contexts($categorycontext); - $thiscontext = $contexts->lowest(); - if ($thiscontext->contextlevel == CONTEXT_COURSE){ - require_login($thiscontext->instanceid, false); - } elseif ($thiscontext->contextlevel == CONTEXT_MODULE){ - list($module, $cm) = get_module_from_cmid($thiscontext->instanceid); - require_login($cm->course, false, $cm); - } - $contexts->require_one_edit_tab_cap($edittab); -} - -$PAGE->set_url($thispageurl); - -$import_form = new question_import_form($thispageurl, array('contexts'=>$contexts->having_one_edit_tab_cap('import'), - 'defaultcategory'=>$pagevars['cat'])); - -if ($import_form->is_cancelled()){ - redirect($thispageurl); -} -//========== -// PAGE HEADER -//========== -$PAGE->set_title($txt->importquestions); -$PAGE->set_heading($COURSE->fullname); -echo $OUTPUT->header(); - -// Print horizontal nav if needed. -$renderer = $PAGE->get_renderer('core_question', 'bank'); -echo $renderer->extra_horizontal_navigation(); - -// file upload form sumitted -if ($form = $import_form->get_data()) { - - // file checks out ok - $fileisgood = false; - - // work out if this is an uploaded file - // or one from the filesarea. - $realfilename = $import_form->get_new_filename('newfile'); - $importfile = make_request_directory() . "/{$realfilename}"; - if (!$result = $import_form->save_file('newfile', $importfile, true)) { - throw new moodle_exception('uploadproblem'); - } - - $formatfile = 'format/' . $form->format . '/format.php'; - if (!is_readable($formatfile)) { - throw new moodle_exception('formatnotfound', 'question', '', $form->format); - } - - require_once($formatfile); - - $classname = 'qformat_' . $form->format; - $qformat = new $classname(); - - // load data into class - $qformat->setCategory($category); - $qformat->setContexts($contexts->having_one_edit_tab_cap('import')); - $qformat->setCourse($COURSE); - $qformat->setFilename($importfile); - $qformat->setRealfilename($realfilename); - $qformat->setMatchgrades($form->matchgrades); - $qformat->setCatfromfile(!empty($form->catfromfile)); - $qformat->setContextfromfile(!empty($form->contextfromfile)); - $qformat->setStoponerror($form->stoponerror); - - // Do anything before that we need to - if (!$qformat->importpreprocess()) { - print_error('cannotimport', '', $thispageurl->out()); - } - - // Process the uploaded file - if (!$qformat->importprocess()) { - print_error('cannotimport', '', $thispageurl->out()); - } - - // In case anything needs to be done after - if (!$qformat->importpostprocess()) { - print_error('cannotimport', '', $thispageurl->out()); - } - - // Log the import into this category. - $eventparams = [ - 'contextid' => $qformat->category->contextid, - 'other' => ['format' => $form->format, 'categoryid' => $qformat->category->id], - ]; - $event = \core\event\questions_imported::create($eventparams); - $event->trigger(); - - $params = $thispageurl->params() + array( - 'category' => $qformat->category->id . ',' . $qformat->category->contextid); - echo $OUTPUT->continue_button(new moodle_url('edit.php', $params)); - echo $OUTPUT->footer(); - exit; -} - -echo $OUTPUT->heading_with_help($txt->importquestions, 'importquestions', 'question'); - -/// Print upload form -$import_form->display(); -echo $OUTPUT->footer(); diff --git a/question/import_form.php b/question/import_form.php index f4693e40184..69e2ff26be0 100644 --- a/question/import_form.php +++ b/question/import_form.php @@ -21,140 +21,7 @@ * @subpackage questionbank * @copyright 2007 Jamie Pratt me@jamiep.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.0 MDL-71585 + * @see \qbank_importquestions\form\question_import_form + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - - -/** - * Form to import questions into the question bank. - * - * @copyright 2007 Jamie Pratt me@jamiep.org - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_import_form extends moodleform { - - protected function definition() { - global $OUTPUT; - - $mform = $this->_form; - - $defaultcategory = $this->_customdata['defaultcategory']; - $contexts = $this->_customdata['contexts']; - - // Choice of import format, with help icons. - $mform->addElement('header', 'fileformat', get_string('fileformat', 'question')); - - $fileformatnames = get_import_export_formats('import'); - $radioarray = array(); - $separators = array(); - foreach ($fileformatnames as $shortname => $fileformatname) { - $radioarray[] = $mform->createElement('radio', 'format', '', $fileformatname, $shortname); - - $separator = ''; - if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) { - $separator .= $OUTPUT->help_icon('pluginname', 'qformat_' . $shortname); - } - $separator .= '
'; - $separators[] = $separator; - } - - $radioarray[] = $mform->createElement('static', 'makelasthelpiconshowup', ''); - $mform->addGroup($radioarray, "formatchoices", '', $separators, false); - $mform->addRule("formatchoices", null, 'required', null, 'client'); - - // Import options. - $mform->addElement('header','general', get_string('general', 'form')); - - $mform->addElement('questioncategory', 'category', get_string('importcategory', 'question'), compact('contexts')); - $mform->setDefault('category', $defaultcategory); - $mform->addHelpButton('category', 'importcategory', 'question'); - - $categorygroup = array(); - $categorygroup[] = $mform->createElement('checkbox', 'catfromfile', '', get_string('getcategoryfromfile', 'question')); - $categorygroup[] = $mform->createElement('checkbox', 'contextfromfile', '', get_string('getcontextfromfile', 'question')); - $mform->addGroup($categorygroup, 'categorygroup', '', '', false); - $mform->disabledIf('categorygroup', 'catfromfile', 'notchecked'); - $mform->setDefault('catfromfile', 1); - $mform->setDefault('contextfromfile', 1); - - $matchgrades = array(); - $matchgrades['error'] = get_string('matchgradeserror', 'question'); - $matchgrades['nearest'] = get_string('matchgradesnearest', 'question'); - $mform->addElement('select', 'matchgrades', get_string('matchgrades', 'question'), $matchgrades); - $mform->addHelpButton('matchgrades', 'matchgrades', 'question'); - $mform->setDefault('matchgrades', 'error'); - - $mform->addElement('selectyesno', 'stoponerror', get_string('stoponerror', 'question')); - $mform->setDefault('stoponerror', 1); - $mform->addHelpButton('stoponerror', 'stoponerror', 'question'); - - // The file to import - $mform->addElement('header', 'importfileupload', get_string('importquestions', 'question')); - - $mform->addElement('filepicker', 'newfile', get_string('import')); - $mform->addRule('newfile', null, 'required', null, 'client'); - - // Submit button. - $mform->addElement('submit', 'submitbutton', get_string('import')); - - // Set a template for the format select elements - $renderer = $mform->defaultRenderer(); - $template = "{help} {element}\n"; - $renderer->setGroupElementTemplate($template, 'format'); - } - - /** - * Checks that a file has been uploaded, and that it is of a plausible type. - * @param array $data the submitted data. - * @param array $errors the errors so far. - * @return array the updated errors. - * @throws moodle_exception - */ - protected function validate_uploaded_file($data, $errors) { - if (empty($data['newfile'])) { - $errors['newfile'] = get_string('required'); - return $errors; - } - - $files = $this->get_draft_files('newfile'); - if (!is_array($files) || count($files) < 1) { - $errors['newfile'] = get_string('required'); - return $errors; - } - - if (empty($data['format'])) { - $errors['format'] = get_string('required'); - return $errors; - } - - $formatfile = 'format/' . $data['format'] . '/format.php'; - if (!is_readable($formatfile)) { - throw new moodle_exception('formatnotfound', 'question', '', $data['format']); - } - - require_once($formatfile); - - $classname = 'qformat_' . $data['format']; - $qformat = new $classname(); - - $file = reset($files); - if (!$qformat->can_import_file($file)) { - $a = new stdClass(); - $a->actualtype = $file->get_mimetype(); - $a->expectedtype = $qformat->mime_type(); - $errors['newfile'] = get_string('importwrongfiletype', 'question', $a); - } - - return $errors; - } - - public function validation($data, $files) { - $errors = parent::validation($data, $files); - $errors = $this->validate_uploaded_file($data, $errors); - return $errors; - } -} diff --git a/question/lib.php b/question/lib.php index a326e1cf087..158bba6cd24 100644 --- a/question/lib.php +++ b/question/lib.php @@ -43,7 +43,6 @@ function core_question_output_fragment_tags_form($args) { if (!empty($args['id'])) { global $CFG, $DB; - require_once($CFG->dirroot . '/question/type/tags_form.php'); require_once($CFG->libdir . '/questionlib.php'); $id = clean_param($args['id'], PARAM_INT); $editingcontext = $args['context']; @@ -88,7 +87,7 @@ function core_question_output_fragment_tags_form($args) { ]; $cantag = question_has_capability_on($question, 'tag'); - $mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data); + $mform = new \qbank_tagquestion\form\tags_form(null, $formoptions, 'post', '', null, $cantag, $data); $mform->set_data($data); return $mform->render(); diff --git a/question/move_form.php b/question/move_form.php index 60fa84caf36..243972c8efa 100644 --- a/question/move_form.php +++ b/question/move_form.php @@ -21,37 +21,7 @@ * @subpackage questionbank * @copyright 2008 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - - -/** - * Form for moving questions between categories. - * - * @copyright 2008 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.0 MDL-71585 * @see qbank_managecategories\form\question_move_form + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ -class question_move_form extends moodleform { - protected function definition() { - debugging('Class question_move_form in \core_question\move_form is deprecated, - please use qbank_managecategories\form\question_move_form instead.', DEBUG_DEVELOPER); - - $mform = $this->_form; - - $currentcat = $this->_customdata['currentcat']; - $contexts = $this->_customdata['contexts']; - - $mform->addElement('questioncategory', 'category', get_string('category', 'question'), compact('contexts', 'currentcat')); - - $this->add_action_buttons(true, get_string('categorymoveto', 'question')); - - $mform->addElement('hidden', 'delete', $currentcat); - $mform->setType('delete', PARAM_INT); - } -} diff --git a/question/preview.php b/question/preview.php deleted file mode 100644 index a7f7affc3a4..00000000000 --- a/question/preview.php +++ /dev/null @@ -1,307 +0,0 @@ -. - -/** - * This page displays a preview of a question - * - * The preview uses the option settings from the activity within which the question - * is previewed or the default settings if no activity is specified. The question session - * information is stored in the session as an array of subsequent states rather - * than in the database. - * - * @package moodlecore - * @subpackage questionengine - * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and - * numerous contributors. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(__DIR__ . '/../config.php'); -require_once($CFG->libdir . '/questionlib.php'); -require_once(__DIR__ . '/previewlib.php'); - -/** - * The maximum number of variants previewable. If there are more variants than this for a question - * then we only allow the selection of the first x variants. - * @var integer - */ -define('QUESTION_PREVIEW_MAX_VARIANTS', 100); - -// Get and validate question id. -$id = required_param('id', PARAM_INT); -$question = question_bank::load_question($id); - -// Were we given a particular context to run the question in? -// This affects things like filter settings, or forced theme or language. -if ($cmid = optional_param('cmid', 0, PARAM_INT)) { - $cm = get_coursemodule_from_id(false, $cmid); - require_login($cm->course, false, $cm); - $context = context_module::instance($cmid); - -} else if ($courseid = optional_param('courseid', 0, PARAM_INT)) { - require_login($courseid); - $context = context_course::instance($courseid); - -} else { - require_login(); - $category = $DB->get_record('question_categories', - array('id' => $question->category), '*', MUST_EXIST); - $context = context::instance_by_id($category->contextid); - $PAGE->set_context($context); - // Note that in the other cases, require_login will set the correct page context. -} -question_require_capability_on($question, 'use'); -$PAGE->set_pagelayout('popup'); - -// Get and validate display options. -$maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); -$options = new qbank_previewquestion\question_preview_options($question); -$options->load_user_defaults(); -$options->set_from_request(); -$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, - $options, $options->variant, $context)); - -// Get and validate existing preview, or start a new one. -$previewid = optional_param('previewid', 0, PARAM_INT); - -if ($previewid) { - try { - $quba = question_engine::load_questions_usage_by_activity($previewid); - - } catch (Exception $e) { - // This may not seem like the right error message to display, but - // actually from the user point of view, it makes sense. - print_error('submissionoutofsequencefriendlymessage', 'question', - question_preview_url($question->id, $options->behaviour, - $options->maxmark, $options, $options->variant, $context), null, $e); - } - - if ($quba->get_owning_context()->instanceid != $USER->id) { - print_error('notyourpreview', 'question'); - } - - $slot = $quba->get_first_question_number(); - $usedquestion = $quba->get_question($slot, false); - if ($usedquestion->id != $question->id) { - print_error('questionidmismatch', 'question'); - } - $question = $usedquestion; - $options->variant = $quba->get_variant($slot); - -} else { - $quba = question_engine::make_questions_usage_by_activity( - 'core_question_preview', context_user::instance($USER->id)); - $quba->set_preferred_behaviour($options->behaviour); - $slot = $quba->add_question($question, $options->maxmark); - - if ($options->variant) { - $options->variant = min($maxvariant, max(1, $options->variant)); - } else { - $options->variant = rand(1, $maxvariant); - } - - $quba->start_question($slot, $options->variant); - - $transaction = $DB->start_delegated_transaction(); - question_engine::save_questions_usage_by_activity($quba); - $transaction->allow_commit(); -} -$options->behaviour = $quba->get_preferred_behaviour(); -$options->maxmark = $quba->get_question_max_mark($slot); - -// Create the settings form, and initialise the fields. -$optionsform = new preview_options_form(question_preview_form_url($question->id, $context, $previewid), - array('quba' => $quba, 'maxvariant' => $maxvariant)); -$optionsform->set_data($options); - -// Process change of settings, if that was requested. -if ($newoptions = $optionsform->get_submitted_data()) { - // Set user preferences. - $options->save_user_preview_options($newoptions); - if (!isset($newoptions->variant)) { - $newoptions->variant = $options->variant; - } - if (isset($newoptions->saverestart)) { - qbank_previewquestion\helper::restart_preview($previewid, $question->id, $newoptions, $context); - } -} - -// Prepare a URL that is used in various places. -$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options, $context); - -// Process any actions from the buttons at the bottom of the form. -if (data_submitted() && confirm_sesskey()) { - - try { - - if (optional_param('restart', false, PARAM_BOOL)) { - qbank_previewquestion\helper::restart_preview($previewid, $question->id, $options, $context); - - } else if (optional_param('fill', null, PARAM_BOOL)) { - $correctresponse = $quba->get_correct_response($slot); - if (!is_null($correctresponse)) { - $quba->process_action($slot, $correctresponse); - - $transaction = $DB->start_delegated_transaction(); - question_engine::save_questions_usage_by_activity($quba); - $transaction->allow_commit(); - } - redirect($actionurl); - - } else if (optional_param('finish', null, PARAM_BOOL)) { - $quba->process_all_actions(); - $quba->finish_all_questions(); - - $transaction = $DB->start_delegated_transaction(); - question_engine::save_questions_usage_by_activity($quba); - $transaction->allow_commit(); - redirect($actionurl); - - } else { - $quba->process_all_actions(); - - $transaction = $DB->start_delegated_transaction(); - question_engine::save_questions_usage_by_activity($quba); - $transaction->allow_commit(); - - $scrollpos = optional_param('scrollpos', '', PARAM_RAW); - if ($scrollpos !== '') { - $actionurl->param('scrollpos', (int) $scrollpos); - } - redirect($actionurl); - } - - } catch (question_out_of_sequence_exception $e) { - print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl); - - } catch (Exception $e) { - // This sucks, if we display our own custom error message, there is no way - // to display the original stack trace. - $debuginfo = ''; - if (!empty($e->debuginfo)) { - $debuginfo = $e->debuginfo; - } - print_error('errorprocessingresponses', 'question', $actionurl, - $e->getMessage(), $debuginfo); - } -} - -if ($question->length) { - $displaynumber = '1'; -} else { - $displaynumber = 'i'; -} -$restartdisabled = array(); -$finishdisabled = array(); -$filldisabled = array(); -if ($quba->get_question_state($slot)->is_finished()) { - $finishdisabled = array('disabled' => 'disabled'); - $filldisabled = array('disabled' => 'disabled'); -} -// If question type cannot give us a correct response, disable this button. -if (is_null($quba->get_correct_response($slot))) { - $filldisabled = array('disabled' => 'disabled'); -} -if (!$previewid) { - $restartdisabled = array('disabled' => 'disabled'); -} - -// Prepare technical info to be output. -$qa = $quba->get_question_attempt($slot); -$technical = array(); -$technical[] = get_string('behaviourbeingused', 'question', - question_engine::get_behaviour_name($qa->get_behaviour_name())); -$technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction()); -$technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); -$technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); -$technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); -$technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); -$technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); -$technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); - -// Start output. -$title = get_string('previewquestion', 'question', format_string($question->name)); -$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot); -$PAGE->set_title($title); -$PAGE->set_heading($title); -echo $OUTPUT->header(); - -// Start the question form. -echo html_writer::start_tag('form', array('method' => 'post', 'action' => $actionurl, - 'enctype' => 'multipart/form-data', 'id' => 'responseform')); -echo html_writer::start_tag('div'); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'slots', 'value' => $slot)); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scrollpos', 'value' => '', 'id' => 'scrollpos')); -echo html_writer::end_tag('div'); - -// Output the question. -echo $quba->render_question($slot, $options, $displaynumber); - -// Finish the question form. -echo html_writer::start_tag('div', array('id' => 'previewcontrols', 'class' => 'controls')); -echo html_writer::empty_tag('input', $restartdisabled + array('type' => 'submit', - 'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1')); -echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', - 'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1', - 'id' => 'id_save_question_preview')); -echo html_writer::empty_tag('input', $filldisabled + array('type' => 'submit', - 'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1')); -echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', - 'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1', - 'id' => 'id_finish_question_preview')); -echo html_writer::end_tag('div'); -echo html_writer::end_tag('form'); - -// Output the technical info. -print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'), - 'core_question_preview_techinfo_collapsed', true, false, $OUTPUT->help_icon('technicalinfo', 'question')); -foreach ($technical as $info) { - echo html_writer::tag('p', $info, array('class' => 'notifytiny')); -} -print_collapsible_region_end(); - -// Output a link to export this single question. -if (question_has_capability_on($question, 'view')) { - if (class_exists('qbank_exporttoxml\\helper')) { - if (\core\plugininfo\qbank::is_plugin_enabled('qbank_exporttoxml')) { - $exportfunction = '\\qbank_exporttoxml\\helper::question_get_export_single_question_url'; - echo html_writer::link($exportfunction($question), - get_string('exportonequestion', 'question')); - } - } else { - $exportfunction = 'question_get_export_single_question_url'; - echo html_writer::link($exportfunction($question), - get_string('exportonequestion', 'question')); - } -} - -// Log the preview of this question. -$event = \core\event\question_viewed::create_from_question_instance($question, $context); -$event->trigger(); - -// Display the settings form. -$optionsform->display(); - -$PAGE->requires->js_module('core_question_engine'); -$PAGE->requires->strings_for_js(array( - 'closepreview', -), 'question'); -$PAGE->requires->yui_module('moodle-question-preview', 'M.question.preview.init'); -$PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_save_question_preview']); -$PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_finish_question_preview']); -echo $OUTPUT->footer(); diff --git a/question/previewlib.php b/question/previewlib.php index ae447ed57fd..398c4dcde41 100644 --- a/question/previewlib.php +++ b/question/previewlib.php @@ -21,211 +21,12 @@ * @subpackage questionengine * @copyright 2010 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - - -/** - * Settings form for the preview options. - * - * @copyright 2009 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.0 * @see qbank_previewquestion\form\preview_options_form - * @todo MDL-71679 class renaming - */ -class preview_options_form extends moodleform { - - public function definition() { - debugging('Class preview_options_form has been deprecated and moved to qbank_previewquestion plugin, - please use qbank_previewquestion\form\preview_options_form instead.', DEBUG_DEVELOPER); - $mform = $this->_form; - - $hiddenofvisible = array( - question_display_options::HIDDEN => get_string('notshown', 'question'), - question_display_options::VISIBLE => get_string('shown', 'question'), - ); - - $mform->addElement('header', 'attemptoptionsheader', get_string('attemptoptions', 'question')); - - $behaviours = question_engine::get_behaviour_options( - $this->_customdata['quba']->get_preferred_behaviour()); - $mform->addElement('select', 'behaviour', - get_string('howquestionsbehave', 'question'), $behaviours); - $mform->addHelpButton('behaviour', 'howquestionsbehave', 'question'); - - $mform->addElement('float', 'maxmark', get_string('markedoutof', 'question'), - array('size' => '5')); - - if ($this->_customdata['maxvariant'] > 1) { - $variants = range(1, $this->_customdata['maxvariant']); - $mform->addElement('select', 'variant', get_string('questionvariant', 'question'), - array_combine($variants, $variants)); - } - $mform->setType('variant', PARAM_INT); - - $mform->addElement('submit', 'saverestart', - get_string('restartwiththeseoptions', 'question')); - - $mform->addElement('header', 'displayoptionsheader', get_string('displayoptions', 'question')); - - $mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'), - $hiddenofvisible); - - $marksoptions = array( - question_display_options::HIDDEN => get_string('notshown', 'question'), - question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'), - question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'), - ); - $mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions); - - $mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'), - question_engine::get_dp_options()); - - $mform->addElement('select', 'feedback', - get_string('specificfeedback', 'question'), $hiddenofvisible); - - $mform->addElement('select', 'generalfeedback', - get_string('generalfeedback', 'question'), $hiddenofvisible); - - $mform->addElement('select', 'rightanswer', - get_string('rightanswer', 'question'), $hiddenofvisible); - - $mform->addElement('select', 'history', - get_string('responsehistory', 'question'), $hiddenofvisible); - - $mform->addElement('submit', 'saveupdate', - get_string('updatedisplayoptions', 'question')); - } -} - - -/** - * Displays question preview options as default and set the options. - * - * Setting default, getting and setting user preferences in question preview options. - * - * @copyright 2010 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 * @see qbank_previewquestion\output\question_preview_options - * @todo MDL-71679 class renaming */ -class question_preview_options extends question_display_options { - /** @var string the behaviour to use for this preview. */ - public $behaviour; - - /** @var number the maximum mark to use for this preview. */ - public $maxmark; - - /** @var int the variant of the question to preview. */ - public $variant; - - /** @var string prefix to append to field names to get user_preference names. */ - const OPTIONPREFIX = 'question_preview_options_'; - - /** - * Constructor. - * @param stdClass $question - */ - public function __construct($question) { - debugging('Class question_preview_options has been deprecated and moved to qbank_previewquestion plugin, - please use qbank_previewquestion\question_preview_options instead.', DEBUG_DEVELOPER); - $this->behaviour = 'deferredfeedback'; - $this->maxmark = $question->defaultmark; - $this->variant = null; - $this->correctness = self::VISIBLE; - $this->marks = self::MARK_AND_MAX; - $this->markdp = get_config('quiz', 'decimalpoints'); - $this->feedback = self::VISIBLE; - $this->numpartscorrect = $this->feedback; - $this->generalfeedback = self::VISIBLE; - $this->rightanswer = self::VISIBLE; - $this->history = self::HIDDEN; - $this->flags = self::HIDDEN; - $this->manualcomment = self::HIDDEN; - } - - /** - * @return array names of the options we store in the user preferences table. - */ - protected function get_user_pref_fields() { - return array('behaviour', 'correctness', 'marks', 'markdp', 'feedback', - 'generalfeedback', 'rightanswer', 'history'); - } - - /** - * @return array names and param types of the options we read from the request. - */ - protected function get_field_types() { - return array( - 'behaviour' => PARAM_ALPHA, - 'maxmark' => PARAM_LOCALISEDFLOAT, - 'variant' => PARAM_INT, - 'correctness' => PARAM_BOOL, - 'marks' => PARAM_INT, - 'markdp' => PARAM_INT, - 'feedback' => PARAM_BOOL, - 'generalfeedback' => PARAM_BOOL, - 'rightanswer' => PARAM_BOOL, - 'history' => PARAM_BOOL, - ); - } - - /** - * Load the value of the options from the user_preferences table. - */ - public function load_user_defaults() { - $defaults = get_config('question_preview'); - foreach ($this->get_user_pref_fields() as $field) { - $this->$field = get_user_preferences( - self::OPTIONPREFIX . $field, $defaults->$field); - } - $this->numpartscorrect = $this->feedback; - } - - /** - * Save a change to the user's preview options to the database. - * @param object $newoptions - */ - public function save_user_preview_options($newoptions) { - foreach ($this->get_user_pref_fields() as $field) { - if (isset($newoptions->$field)) { - set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field); - } - } - } - - /** - * Set the value of any fields included in the request. - */ - public function set_from_request() { - foreach ($this->get_field_types() as $field => $type) { - $this->$field = optional_param($field, $this->$field, $type); - } - $this->numpartscorrect = $this->feedback; - } - - /** - * @return string URL fragment. Parameters needed in the URL when continuing - * this preview. - */ - public function get_url_params() { - $params = array(); - foreach ($this->get_field_types() as $field => $notused) { - if ($field == 'behaviour' || $field == 'maxmark' || is_null($this->$field)) { - continue; - } - $params[$field] = $this->$field; - } - return $params; - } -} +use qbank_previewquestion\question_preview_options; /** * Called via pluginfile.php -> question_pluginfile to serve files belonging to diff --git a/question/question.php b/question/question.php deleted file mode 100644 index 1fe9b06ae9b..00000000000 --- a/question/question.php +++ /dev/null @@ -1,346 +0,0 @@ -. - -/** - * Page for editing questions. - * - * @package moodlecore - * @subpackage questionbank - * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(__DIR__ . '/../config.php'); -require_once(__DIR__ . '/editlib.php'); -require_once($CFG->libdir . '/filelib.php'); -require_once($CFG->libdir . '/formslib.php'); - -// Read URL parameters telling us which question to edit. -$id = optional_param('id', 0, PARAM_INT); // question id -$makecopy = optional_param('makecopy', 0, PARAM_BOOL); -$qtype = optional_param('qtype', '', PARAM_COMPONENT); -$categoryid = optional_param('category', 0, PARAM_INT); -$cmid = optional_param('cmid', 0, PARAM_INT); -$courseid = optional_param('courseid', 0, PARAM_INT); -$wizardnow = optional_param('wizardnow', '', PARAM_ALPHA); -$originalreturnurl = optional_param('returnurl', 0, PARAM_LOCALURL); -$appendqnumstring = optional_param('appendqnumstring', '', PARAM_ALPHA); -$inpopup = optional_param('inpopup', 0, PARAM_BOOL); -$scrollpos = optional_param('scrollpos', 0, PARAM_INT); - -$url = new moodle_url('/question/question.php'); -if ($id !== 0) { - $url->param('id', $id); -} -if ($makecopy) { - $url->param('makecopy', $makecopy); -} -if ($qtype !== '') { - $url->param('qtype', $qtype); -} -if ($categoryid !== 0) { - $url->param('category', $categoryid); -} -if ($cmid !== 0) { - $url->param('cmid', $cmid); -} -if ($courseid !== 0) { - $url->param('courseid', $courseid); -} -if ($wizardnow !== '') { - $url->param('wizardnow', $wizardnow); -} -if ($originalreturnurl !== 0) { - $url->param('returnurl', $originalreturnurl); -} -if ($appendqnumstring !== '') { - $url->param('appendqnumstring', $appendqnumstring); -} -if ($inpopup !== 0) { - $url->param('inpopup', $inpopup); -} -if ($scrollpos) { - $url->param('scrollpos', $scrollpos); -} -$PAGE->set_url($url); - -if ($cmid) { - $questionbankurl = new moodle_url('/question/edit.php', array('cmid' => $cmid)); -} else { - $questionbankurl = new moodle_url('/question/edit.php', array('courseid' => $courseid)); -} -navigation_node::override_active_url($questionbankurl); - -if ($originalreturnurl) { - if (strpos($originalreturnurl, '/') !== 0) { - throw new coding_exception("returnurl must be a local URL starting with '/'. $originalreturnurl was given."); - } - $returnurl = new moodle_url($originalreturnurl); -} else { - $returnurl = $questionbankurl; -} -if ($scrollpos) { - $returnurl->param('scrollpos', $scrollpos); -} - -if ($cmid){ - list($module, $cm) = get_module_from_cmid($cmid); - require_login($cm->course, false, $cm); - $thiscontext = context_module::instance($cmid); -} elseif ($courseid) { - require_login($courseid, false); - $thiscontext = context_course::instance($courseid); - $module = null; - $cm = null; -} else { - print_error('missingcourseorcmid', 'question'); -} -$contexts = new question_edit_contexts($thiscontext); -$PAGE->set_pagelayout('admin'); - -if (optional_param('addcancel', false, PARAM_BOOL)) { - redirect($returnurl); -} - -if ($id) { - if (!$question = $DB->get_record('question', array('id' => $id))) { - print_error('questiondoesnotexist', 'question', $returnurl); - } - // We can use $COURSE here because it's been initialised as part of the - // require_login above. Passing it as the third parameter tells the function - // to filter the course tags by that course. - get_question_options($question, true, [$COURSE]); - -} else if ($categoryid && $qtype) { // only for creating new questions - $question = new stdClass(); - $question->category = $categoryid; - $question->qtype = $qtype; - $question->createdby = $USER->id; - - // Check that users are allowed to create this question type at the moment. - if (!question_bank::qtype_enabled($qtype)) { - print_error('cannotenable', 'question', $returnurl, $qtype); - } - -} else if ($categoryid) { - // Category, but no qtype. They probably came from the addquestion.php - // script without choosing a question type. Send them back. - $addurl = new moodle_url('/question/addquestion.php', $url->params()); - $addurl->param('validationerror', 1); - redirect($addurl); - -} else { - print_error('notenoughdatatoeditaquestion', 'question', $returnurl); -} - -$qtypeobj = question_bank::get_qtype($question->qtype); - -if (isset($question->categoryobject)) { - $category = $question->categoryobject; -} else { - // Validate the question category. - if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) { - print_error('categorydoesnotexist', 'question', $returnurl); - } -} - -// Check permissions -$question->formoptions = new stdClass(); - -$categorycontext = context::instance_by_id($category->contextid); -$question->contextid = $category->contextid; -$addpermission = has_capability('moodle/question:add', $categorycontext); - -if ($id) { - $question->formoptions->canedit = question_has_capability_on($question, 'edit'); - $question->formoptions->canmove = $addpermission && question_has_capability_on($question, 'move'); - $question->formoptions->cansaveasnew = $addpermission && - (question_has_capability_on($question, 'view') || $question->formoptions->canedit); - $question->formoptions->repeatelements = $question->formoptions->canedit || $question->formoptions->cansaveasnew; - $formeditable = $question->formoptions->canedit || $question->formoptions->cansaveasnew || $question->formoptions->canmove; - if (!$formeditable) { - question_require_capability_on($question, 'view'); - } - if ($makecopy) { - // If we are duplicating a question, add some indication to the question name. - $question->name = get_string('questionnamecopy', 'question', $question->name); - $question->idnumber = core_question_find_next_unused_idnumber($question->idnumber, $category->id); - $question->beingcopied = true; - } - -} else { // creating a new question - $question->formoptions->canedit = question_has_capability_on($question, 'edit'); - $question->formoptions->canmove = (question_has_capability_on($question, 'move') && $addpermission); - $question->formoptions->cansaveasnew = false; - $question->formoptions->repeatelements = true; - $formeditable = true; - require_capability('moodle/question:add', $categorycontext); -} -$question->formoptions->mustbeusable = (bool) $appendqnumstring; - -// Validate the question type. -$PAGE->set_pagetype('question-type-' . $question->qtype); - -// Create the question editing form. -if ($wizardnow !== '') { - $mform = $qtypeobj->next_wizard_form('question.php', $question, $wizardnow, $formeditable); -} else { - $mform = $qtypeobj->create_editing_form('question.php', $question, $category, $contexts, $formeditable); -} -$toform = fullclone($question); // send the question object and a few more parameters to the form -$toform->category = "{$category->id},{$category->contextid}"; -$toform->scrollpos = $scrollpos; -if ($formeditable && $id){ - $toform->categorymoveto = $toform->category; -} - -$toform->appendqnumstring = $appendqnumstring; -$toform->returnurl = $originalreturnurl; -$toform->makecopy = $makecopy; -if ($cm !== null){ - $toform->cmid = $cm->id; - $toform->courseid = $cm->course; -} else { - $toform->courseid = $COURSE->id; -} - -$toform->inpopup = $inpopup; - -$mform->set_data($toform); - -if ($mform->is_cancelled()) { - if ($inpopup) { - close_window(); - } else { - redirect($returnurl); - } - -} else if ($fromform = $mform->get_data()) { - // If we are saving as a copy, break the connection to the old question. - if ($makecopy) { - $question->id = 0; - $question->hidden = 0; // Copies should not be hidden. - } - - /// Process the combination of usecurrentcat, categorymoveto and category form - /// fields, so the save_question method only has to consider $fromform->category - if (!empty($fromform->usecurrentcat)) { - // $fromform->category is the right category to save in. - } else { - if (!empty($fromform->categorymoveto)) { - $fromform->category = $fromform->categorymoveto; - } else { - // $fromform->category is the right category to save in. - } - } - - /// If we are moving a question, check we have permission to move it from - /// whence it came. (Where we are moving to is validated by the form.) - list($newcatid, $newcontextid) = explode(',', $fromform->category); - if (!empty($question->id) && $newcatid != $question->category) { - $contextid = $newcontextid; - question_require_capability_on($question, 'move'); - } else { - $contextid = $category->contextid; - } - - // Ensure we redirect back to the category the question is being saved into. - $returnurl->param('category', $fromform->category); - - // We are actually saving the question. - if (!empty($question->id)) { - question_require_capability_on($question, 'edit'); - } else { - require_capability('moodle/question:add', context::instance_by_id($contextid)); - if (!empty($fromform->makecopy) && !$question->formoptions->cansaveasnew) { - print_error('nopermissions', '', '', 'edit'); - } - } - - // If this is a new question, save defaults for user in user_preferences table. - if (empty($question->id)) { - $qtypeobj->save_defaults_for_new_questions($fromform); - } - $question = $qtypeobj->save_question($question, $fromform); - if (isset($fromform->tags)) { - // If we have any question context level tags then set those tags now. - core_tag_tag::set_item_tags('core_question', 'question', $question->id, - context::instance_by_id($contextid), $fromform->tags, 0); - } - - if (isset($fromform->coursetags)) { - // If we have and course context level tags then set those now. - core_tag_tag::set_item_tags('core_question', 'question', $question->id, - context_course::instance($fromform->courseid), $fromform->coursetags, 0); - } - - // Purge this question from the cache. - question_bank::notify_question_edited($question->id); - - // If we are saving and continuing to edit the question. - if (!empty($fromform->updatebutton)) { - $url->param('id', $question->id); - $url->remove_params('makecopy'); - redirect($url); - } - - if ($qtypeobj->finished_edit_wizard($fromform)) { - if ($inpopup) { - echo $OUTPUT->notification(get_string('changessaved'), ''); - close_window(3); - } else { - $returnurl->param('lastchanged', $question->id); - if ($appendqnumstring) { - $returnurl->param($appendqnumstring, $question->id); - $returnurl->param('sesskey', sesskey()); - $returnurl->param('cmid', $cmid); - } - redirect($returnurl); - } - - } else { - $nexturlparams = array( - 'returnurl' => $originalreturnurl, - 'appendqnumstring' => $appendqnumstring, - 'scrollpos' => $scrollpos); - if (isset($fromform->nextpageparam) && is_array($fromform->nextpageparam)){ - //useful for passing data to the next page which is not saved in the database. - $nexturlparams += $fromform->nextpageparam; - } - $nexturlparams['id'] = $question->id; - $nexturlparams['wizardnow'] = $fromform->wizard; - $nexturl = new moodle_url('/question/question.php', $nexturlparams); - if ($cmid){ - $nexturl->param('cmid', $cmid); - } else { - $nexturl->param('courseid', $COURSE->id); - } - redirect($nexturl); - } - -} - -$streditingquestion = $qtypeobj->get_heading(); -$PAGE->set_title($streditingquestion); -$PAGE->set_heading($COURSE->fullname); -$PAGE->navbar->add($streditingquestion); - -// Display a heading, question editing form and possibly some extra content needed for -// for this question type. -echo $OUTPUT->header(); -$qtypeobj->display_question_editing_page($mform, $question, $wizardnow); -echo $OUTPUT->footer(); diff --git a/question/renderer.php b/question/renderer.php index 3ca2feb5e21..7b8ca0bb0a2 100644 --- a/question/renderer.php +++ b/question/renderer.php @@ -98,9 +98,11 @@ class core_question_bank_renderer extends plugin_renderer_base { * @return string * @deprecated since Moodle 4.0 * @see \qbank_editquestion\output\renderer - * @todo MDL-72004 deprecate the function and add debugging message. + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function render_qbank_chooser(renderable $qbankchooser) { + debugging('Function render_qbank_chooser is deprecated, + please use qbank_editquestion renderer instead.', DEBUG_DEVELOPER); return $this->render_from_template('core_question/qbank_chooser', $qbankchooser->export_for_template($this)); } @@ -162,6 +164,7 @@ class core_question_bank_renderer extends plugin_renderer_base { * @param object $course The course that will be displayed * @param array $hiddenparams Any hidden parameters to add to the form * @return string The composed HTML for the questionbank chooser + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function qbank_chooser($real, $fake, $course, $hiddenparams) { debugging('Method core_question_bank_renderer::qbank_chooser() is deprecated, ' . @@ -174,6 +177,7 @@ class core_question_bank_renderer extends plugin_renderer_base { * * @param array $types A set of question types as used by the qbank_chooser_module function * @return string The composed HTML for the module + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ protected function qbank_chooser_types($types) { debugging('Method core_question_bank_renderer::qbank_chooser_types() is deprecated, ' . @@ -188,6 +192,7 @@ class core_question_bank_renderer extends plugin_renderer_base { * If the module contains subtypes in the types option, then these will also be displayed. * @param array $classes Additional classes to add to the encompassing div element * @return string The composed HTML for the question type + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ protected function qbank_chooser_qtype($qtype, $classes = array()) { debugging('Method core_question_bank_renderer::qbank_chooser_qtype() is deprecated, ' . @@ -201,6 +206,7 @@ class core_question_bank_renderer extends plugin_renderer_base { * @param string $title The language string identifier * @param string $identifier The component identifier * @return string The composed HTML for the title + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ protected function qbank_chooser_title($title, $identifier = null) { debugging('Method core_question_bank_renderer::qbank_chooser_title() is deprecated, ' . diff --git a/question/type/calculated/datasetitems_form.php b/question/type/calculated/datasetitems_form.php index f566ad60cfd..e0e9b71809c 100644 --- a/question/type/calculated/datasetitems_form.php +++ b/question/type/calculated/datasetitems_form.php @@ -337,16 +337,9 @@ class question_dataset_dependent_items_form extends question_wizard_form { $buttonarray = []; $buttonarray[] = $mform->createElement( 'submit', 'savechanges', get_string('savechanges')); - - // Todo MDL-72004 changes for class renaming and default sort. - if (class_exists('qbank_previewquestion\\preview_action_column')) { - if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) { - $previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link( - $this->question->id, $this->categorycontext, true); - } - } else { - $previewlink = $PAGE->get_renderer('core_question')->question_preview_link( - $this->question->id, $this->categorycontext, true); + if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) { + $previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link($this->question->id, + $this->categorycontext, true); } $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink); diff --git a/question/type/tags_form.php b/question/type/tags_form.php deleted file mode 100644 index d3c22c8630c..00000000000 --- a/question/type/tags_form.php +++ /dev/null @@ -1,100 +0,0 @@ -. - -/** - * The mform to manage question tags. - * - * @package core_question - * @copyright 2018 Simey Lameze - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_question\form; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/lib/formslib.php'); -require_once($CFG->dirroot . '/lib/questionlib.php'); -/** - * The mform class for manage question tags. - * - * @copyright 2018 Simey Lameze - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @deprecated since Moodle 4.0 - * @see \qbank_tagquestion\form\tags_form - * @todo MDL-71679 class renaming - */ -class tags extends \moodleform { - - /** - * The form definition - */ - public function definition() { - debugging('Class column_base in core_question\form\tags is deprecated, - please use qbank_tagquestion\form\tags_form instead.', DEBUG_DEVELOPER); - $mform = $this->_form; - $customdata = $this->_customdata; - - $mform->disable_form_change_checker(); - - $mform->addElement('hidden', 'id'); - $mform->setType('id', PARAM_INT); - - $mform->addElement('hidden', 'categoryid'); - $mform->setType('categoryid', PARAM_INT); - - $mform->addElement('hidden', 'contextid'); - $mform->setType('contextid', PARAM_INT); - - $mform->addElement('static', 'questionname', get_string('questionname', 'question')); - $mform->addElement('static', 'questioncategory', get_string('categorycurrent', 'question')); - $mform->addElement('static', 'context', ''); - - if (\core_tag_tag::is_enabled('core_question', 'question')) { - $tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $customdata['contexts']); - $tagstrings = []; - foreach ($tags as $tag) { - $tagstrings[$tag->name] = $tag->name; - } - - $options = [ - 'tags' => true, - 'multiple' => true, - 'noselectionstring' => get_string('anytags', 'quiz'), - ]; - $mform->addElement('autocomplete', 'tags', get_string('tags'), $tagstrings, $options); - - // Is the question category in a course context? - $qcontext = $customdata['questioncontext']; - $qcoursecontext = $qcontext->get_course_context(false); - $iscourseoractivityquestion = !empty($qcoursecontext); - // Is the current context we're editing in a course context? - $editingcontext = $customdata['editingcontext']; - $editingcoursecontext = $editingcontext->get_course_context(false); - $iseditingcontextcourseoractivity = !empty($editingcoursecontext); - - if ($iseditingcontextcourseoractivity && !$iscourseoractivityquestion) { - // If the question is being edited in a course or activity context - // and the question isn't a course or activity level question then - // allow course tags to be added to the course. - $coursetagheader = get_string('questionformtagheader', 'core_question', - $editingcoursecontext->get_context_name(true)); - $mform->addElement('autocomplete', 'coursetags', $coursetagheader, $tagstrings, $options); - - } - } - } -} diff --git a/question/upgrade.txt b/question/upgrade.txt index 8903e6a6ea0..0bddd1d756a 100644 --- a/question/upgrade.txt +++ b/question/upgrade.txt @@ -6,6 +6,7 @@ This files describes API changes for code that uses the question API. are divided in two different parts, base classes and feature classes. All the base classes are moved classes/local/bank and all the feature classes will be moved to the plugin for that feature. + 2) submit_tags_form and associated external services for question tag, tags_form in question/type, core_question_output_fragment_tags_form method in lib is deprecated and moved to the tagquestion plugin. @@ -19,6 +20,59 @@ This files describes API changes for code that uses the question API. The following classes are deprecated in previewlib and moves the previewquestion plugin: question_preview_options, preview_options_form. +5) submit_tags_form and associated external services for question tag, tags_form in question/type, + core_question_output_fragment_tags_form method in lib is deprecated and moved to the tagquestion plugin. + +6) Function question_get_export_single_question_url() in questionlib has been deprecated + and moved to qbank_exporttoxml plugin, the new location is: + qbank_exporttoxml\exporttoxml_helper::question_get_export_single_question_url(). + +7) The following classes are deleted and available in the new location: + 'core_question\bank\action_column_base' => 'core_question\local\bank\action_column_base', + 'core_question\bank\checkbox_column' => 'core_question\local\bank\checkbox_column', + 'core_question\bank\column_base' => 'core_question\local\bank\column_base', + 'core_question\bank\edit_menu_column' => 'core_question\local\bank\edit_menu_column', + 'core_question\bank\menu_action_column_base' => 'core_question\local\bank\menu_action_column_base', + 'core_question\bank\menuable_action' => 'core_question\local\bank\menuable_action', + 'core_question\bank\random_question_loader' => 'core_question\local\bank\random_question_loader', + 'core_question\bank\row_base' => 'core_question\local\bank\row_base', + 'core_question\bank\view' => 'core_question\local\bank\view', + 'core_question\bank\copy_action_column' => 'qbank_editquestion\copy_action_column', + 'core_question\bank\edit_action_column' => 'qbank_editquestion\edit_action_column', + 'core_question\bank\creator_name_column' => 'qbank_viewcreator\creator_name_column', + 'core_question\bank\modifier_name_column' => 'qbank_viewcreator\modifier_name_column', + 'core_question\bank\question_name_column' => 'qbank_viewquestionname\viewquestionname_column_helper', + 'core_question\bank\question_name_idnumber_tags_column' => 'qbank_viewquestionname\question_name_idnumber_tags_column', + 'core_question\bank\delete_action_column' => 'qbank_deletequestion\delete_action_column', + 'core_question\bank\export_xml_action_column' => 'qbank_exporttoxml\export_xml_action_column', + 'core_question\bank\preview_action_column' => 'qbank_previewquestion\preview_action_column', + 'core_question\bank\question_text_row' => 'qbank_viewquestiontext\question_text_row', + 'core_question\bank\question_type_column' => 'qbank_viewquestiontype\question_type_column', + 'core_question\bank\tags_action_column' => 'qbank_tagquestion\tags_action_column', + 'core_question\form\tags' => '\qbank_tagquestion\form\tags_form', + 'core_question\output\qbank_chooser' => 'qbank_editquestion\qbank_chooser', + 'core_question\output\qbank_chooser_item' => 'qbank_editquestion\qbank_chooser_item', + +8) The following classes are deprecated and moved to a new location. These are marked for final deprecation on 4.4: + 'question_category_list' => 'qbank_managecategories\question_category_list', + 'question_category_list_item' => 'qbank_managecategories\question_category_list_item', + 'question_category_object' => 'qbank_managecategories\question_category_object', + 'question_category_edit_form' => 'qbank_managecategories\form\category_form', + 'question_export_form' => 'qbank_exportquestions\form\export_form', + 'question_import_form' => 'qbank_importquestions\form\question_import_form', + 'question_move_form' => 'qbank_managecategories\form\question_move_form', + 'preview_options_form' => 'qbank_previewquestion\form\preview_options_form', + 'question_preview_options' => 'qbank_previewquestion\output\question_preview_options', + 'category_form' => 'qbank_managecategories\form\category_form', + 'export_form' => 'qbank_exportquestions\form\export_form'. + +9) The following functions are deprecated and moved to the new location: + question_preview_question_pluginfile() => qbank_previewquestion\helper::question_preview_question_pluginfile(), + question_preview_action_url() => qbank_previewquestion\helper::question_preview_action_url(), + question_preview_form_url() => qbank_previewquestion\helper::question_preview_form_url(), + restart_preview() => qbank_previewquestion\helper::restart_preview(), + core_question_output_fragment_tags_form() => /question/bank/qbank_tagquestion/lib.php. + === 3.9 == 1) For years, the ..._questions_in_use callback has been the right way for plugins to