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 5f054c1316c..9f5db3e4c91 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -71,6 +71,11 @@ information provided here is intended especially for developers. that wants to listen for the events. Backwards compatibility with existing code using jQuery is preserved. * 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