diff --git a/lang/en/question.php b/lang/en/question.php index 27b569dd025..3a9caaead44 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -25,6 +25,7 @@ $string['addmorechoiceblanks'] = 'Blanks for {no} more choices'; $string['addcategory'] = 'Add category'; $string['adminreport'] = 'Report on possible problems in your question database.'; +$string['advancedsearchoptions'] = 'Search options'; $string['answers'] = 'Answers'; $string['availableq'] = 'Available?'; $string['badbase'] = 'Bad base before **: {$a}**'; diff --git a/mod/quiz/editlib.php b/mod/quiz/editlib.php index 6cebed780e2..e3d9f8a6447 100644 --- a/mod/quiz/editlib.php +++ b/mod/quiz/editlib.php @@ -1111,6 +1111,7 @@ class quiz_question_bank_view extends question_bank_view { protected $quizhasattempts = false; /** @var object the quiz settings. */ protected $quiz = false; + const MAX_TEXT_LENGTH = 200; /** * Constructor @@ -1184,16 +1185,15 @@ class quiz_question_bank_view extends question_bank_view { return; } - // Display the current category. - if (!$category = $this->get_current_category($cat)) { - return; - } - $this->print_category_info($category); + $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); + array_unshift($this->searchconditions, + new question_bank_search_condition_hide(!$showhidden)); + array_unshift($this->searchconditions, + new question_bank_search_condition_category($cat, $recurse, + $editcontexts, $this->baseurl, $this->course, self::MAX_TEXT_LENGTH)); echo $OUTPUT->box_start('generalbox questionbank'); - - $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname), - $this->baseurl, $cat); + $this->display_options_form($showquestiontext); // Continues with list of questions. $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname), @@ -1201,12 +1201,18 @@ class quiz_question_bank_view extends question_bank_view { $perpage, $showhidden, $showquestiontext, $this->contexts->having_cap('moodle/question:add')); - $this->display_options($recurse, $showhidden, $showquestiontext); echo $OUTPUT->box_end(); } + /** + * prints a form to choose categories + * @deprecated since Moodle 2.6 MDL-40313. + * @see question_bank_search_condition_category + * @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 question_bank_search_condition_category instead.', DEBUG_DEVELOPER); echo $OUTPUT->box_start('generalbox questionbank'); $this->display_category_form($this->contexts->having_one_edit_tab_cap('edit'), $this->baseurl, $categoryandcontext); @@ -1216,6 +1222,27 @@ class quiz_question_bank_view extends question_bank_view { echo $OUTPUT->box_end(); } + /** + * Display the form with options for which questions are displayed and how they are displayed. + * This differs from parent display_options_form only in that it does not have the checkbox to show the question text. + * @param bool $showquestiontext Display the text of the question within the list. (Currently ignored) + */ + protected function display_options_form($showquestiontext) { + global $PAGE; + echo html_writer::start_tag('form', array('method' => 'get', + 'action' => new moodle_url('/mod/quiz/edit.php'), 'id' => 'displayoptions')); + echo html_writer::start_div(); + foreach ($this->searchconditions as $searchcondition) { + echo $searchcondition->display_options($this); + } + $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::tag('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'); + } + protected function print_category_info($category) { $formatoptions = new stdClass(); $formatoptions->noclean = true; @@ -1232,6 +1259,7 @@ class quiz_question_bank_view extends question_bank_view { } 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, diff --git a/question/classes/bank_search_condition.php b/question/classes/bank_search_condition.php new file mode 100755 index 00000000000..943ba82efd0 --- /dev/null +++ b/question/classes/bank_search_condition.php @@ -0,0 +1,39 @@ +display() + * @param boolean $recurse Whether to include questions from sub-categories + * @param array $contexts Context objects as used by question_category_options() + * @param moodle_url $baseurl The URL the form is submitted to + * @param stdClass $course Course record + * @param integer $maxinfolength The maximum displayed length of the category info + */ + public function __construct($cat = null, $recurse = false, $contexts, $baseurl, $course, $maxinfolength = null) { + $this->cat = $cat; + $this->recurse = $recurse; + $this->contexts = $contexts; + $this->baseurl = $baseurl; + $this->course = $course; + $this->init(); + $this->maxinfolength = $maxinfolength; + } + + /** + * Initialize the object so it will be ready to return where() and params() + */ + private function init() { + global $DB; + if (!$this->category = $this->get_current_category($this->cat)) { + return; + } + if ($this->recurse) { + $categoryids = question_categorylist($this->category->id); + } else { + $categoryids = array($this->category->id); + } + list($catidtest, $this->params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat'); + $this->where = 'q.category ' . $catidtest; + } + + /** + * @returns string SQL fragment to be ANDed to the where clause to select which category of questions to display + */ + public function where() { + return $this->where; + } + + /** + * @returns array Parameters to be bound to the SQL query to select which category of questions to display + */ + public function params() { + return $this->params; + } + + /** + * Called by question_bank_view to display the GUI for selecting a category + */ + public function display_options() { + $this->display_category_form($this->contexts, $this->baseurl, $this->cat); + $this->print_category_info($this->category); + } + + /** + * Displays the recursion checkbox GUI. + * question_bank_view places this within the section that is hidden by default + */ + public function display_options_adv() { + echo '
'; + echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'recurse', + 'value' => 0, 'id' => 'recurse_off')); + echo html_writer::checkbox('recurse', '1', $this->recurse, get_string('includesubcategories', 'question'), + array('id' => 'recurse_on', 'class' => 'searchoptions')); + echo "
\n"; + + } + + /** + * Display the drop down to select the category + */ + protected function display_category_form($contexts, $pageurl, $current) { + global $OUTPUT; + + 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"; + + } + + /** + * Look up the category record based on cateogry ID and context + * @param string $categoryandcontext categoryID,contextID as used with question_bank_view->display() + * @return stdClass The category record + */ + 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; + } + + /** + * Print the category description + */ + protected function print_category_info($category) { + $formatoptions = new stdClass(); + $formatoptions->noclean = true; + $formatoptions->overflowdiv = true; + echo '
'; + if (isset($this->maxinfolength)) { + echo shorten_text(format_text($category->info, $category->infoformat, $formatoptions, $this->course->id), + $this->maxinfolength); + } else { + echo format_text($category->info, $category->infoformat, $formatoptions, $this->course->id); + } + echo "
\n"; + } + +} + diff --git a/question/classes/bank_search_condition_hide.php b/question/classes/bank_search_condition_hide.php new file mode 100755 index 00000000000..fef44bf3181 --- /dev/null +++ b/question/classes/bank_search_condition_hide.php @@ -0,0 +1,37 @@ +hide = $hide; + if ($hide) { + $this->where = 'q.hidden = 0'; + } + } + + /** + * @return string An SQL fragment to be ANDed into the WHERE clause to show or hide deleted/hidden questions + */ + public function where() { + return $this->where; + } + + /** + * Print HTML to display the "Also show old questions" checkbox + */ + public function display_options_adv() { + echo "
"; + echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'showhidden', + 'value' => '0', 'id' => 'showhidden_off')); + echo html_writer::checkbox('showhidden', '1', (! $this->hide), get_string('showhidden', 'question'), + array('id' => 'showhidden_on', 'class' => 'searchoptions')); + echo "
\n"; + } +} diff --git a/question/editlib.php b/question/editlib.php index 3f39887ba43..330535cd7a5 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -898,6 +898,7 @@ class question_bank_view { protected $countsql; protected $loadsql; protected $sqlparams; + protected $searchconditions = array(); /** * Constructor @@ -935,6 +936,20 @@ class question_bank_view { $this->init_column_types(); $this->init_columns($this->wanted_columns(), $this->heading_column()); $this->init_sort(); + $this->init_search_conditions($this->contexts, $this->course, $this->cm); + } + + /** + * 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() { @@ -1141,7 +1156,23 @@ class question_bank_view { return $this->baseurl->out(true, $this->sort_to_params($newsort)); } + /** + * Create the SQL query to retrieve the indicated questions + * @deprecated since Moodle 2.7 MDL-40313. + * @see build_query() + * @see 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 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() { global $DB; /// Get the required tables. @@ -1175,26 +1206,20 @@ class question_bank_view { /// Build the where clause. $tests = array('q.parent = 0'); - - if (!$showhidden) { - $tests[] = 'q.hidden = 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()); + } } - - if ($recurse) { - $categoryids = question_categorylist($category->id); - } else { - $categoryids = array($category->id); - } - list($catidtest, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat'); - $tests[] = 'q.category ' . $catidtest; - $this->sqlparams = $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); - $this->sqlparams = $params; } protected function get_question_count() { @@ -1248,23 +1273,18 @@ class question_bank_view { if ($this->process_actions_needing_ui()) { return; } - + $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); // Category selection form echo $OUTPUT->heading(get_string('questionbank', 'question'), 2); - - $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname), - $this->baseurl, $cat); - $this->display_options($recurse, $showhidden, $showquestiontext); - - if (!$category = $this->get_current_category($cat)) { - return; - } - $this->print_category_info($category); + array_unshift($this->searchconditions, new core_question_bank_search_condition_hide(!$showhidden)); + array_unshift($this->searchconditions, new core_question_bank_search_condition_category($cat, $recurse, $editcontexts, + $this->baseurl, $this->course)); + $this->display_options_form($showquestiontext); // continues with list of questions $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname), $this->baseurl, $cat, $this->cm, - $recurse, $page, $perpage, $showhidden, $showquestiontext, + null, $page, $perpage, $showhidden, $showquestiontext, $this->contexts->having_cap('moodle/question:add')); } @@ -1293,6 +1313,12 @@ class question_bank_view { return $category; } + /** + * prints category information + * @deprecated since Moodle 2.7 MDL-40313. + * @see core_question_bank_search_condition_category + * @todo MDL-41978 This will be deleted in Moodle 2.8 + */ protected function print_category_info($category) { $formatoptions = new stdClass(); $formatoptions->noclean = true; @@ -1304,10 +1330,14 @@ class question_bank_view { /** * prints a form to choose categories + * @deprecated since Moodle 2.7 MDL-40313. + * @see core_question_bank_search_condition_category + * @todo MDL-41978 This will be deleted in Moodle 2.8 */ protected function display_category_form($contexts, $pageurl, $current) { - global $CFG, $OUTPUT; + global $OUTPUT; + debugging('display_category_form() is deprecated, please use core_question_bank_search_condition_category instead.', DEBUG_DEVELOPER); /// Get all the existing categories now echo '
'; $catmenu = question_category_options($contexts, false, 0, true); @@ -1318,21 +1348,26 @@ class question_bank_view { echo "
\n"; } + /** + * @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_category + */ protected function display_options($recurse, $showhidden, $showquestiontext) { - 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')); - $this->display_category_form_checkbox('qbshowtext', $showquestiontext, get_string('showquestiontext', 'question')); - echo '
'; + debugging('display_options() is deprecated, please use display_options_form instead.', DEBUG_DEVELOPER); + return $this->display_options_form($showquestiontext); } /** - * Print a single option checkbox. Used by the preceeding. + * Print a single option checkbox. + * @deprecated since Moodle 2.7 MDL-40313. + * @see core_question_bank_search_condition_category + * @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_category instead.', DEBUG_DEVELOPER); 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. + */ + protected function display_options_form($showquestiontext) { + global $PAGE; + + echo '
'; + echo "
"; + echo html_writer::input_hidden_params($this->baseurl, array('recurse', 'showhidden', 'qbshowtext')); + + foreach ($this->searchconditions as $searchcondition) { + echo $searchcondition->display_options($this); + } + $this->display_showtext_checkbox($showquestiontext); + $this->display_advanced_search_form(); + $PAGE->requires->yui_module('moodle-question-searchform', 'M.question.searchform.init'); + echo '
'; + } + + /** + * 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($this); + } + 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')); + echo "
\n"; + } + protected function create_new_question_form($category, $canadd) { global $CFG; echo '
'; @@ -1365,7 +1446,7 @@ class question_bank_view { * @param int $page The number of the page to be displayed * @param int $perpage Number of questions to show per page * @param bool $showhidden True if also hidden questions should be displayed - * @param bool $showquestiontext whether the text of each question should be shown in the list + * @param bool $showquestiontext whether the text of each question should be shown in the list. Deprecated. */ protected function display_question_list($contexts, $pageurl, $categoryandcontext, $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false, @@ -1391,7 +1472,7 @@ class question_bank_view { $this->create_new_question_form($category, $canadd); - $this->build_query_sql($category, $recurse, $showhidden); + $this->build_query(); $totalnumber = $this->get_question_count(); if ($totalnumber == 0) { return; @@ -1409,7 +1490,7 @@ class question_bank_view { echo '
'; echo '
'; echo ''; - echo html_writer::input_hidden_params($pageurl); + echo html_writer::input_hidden_params($this->baseurl); echo '
'; $this->start_table(); @@ -1625,6 +1706,10 @@ class question_bank_view { return true; } } + + public function add_searchcondition($searchcondition) { + $this->searchconditions[] = $searchcondition; + } } /** diff --git a/question/upgrade.txt b/question/upgrade.txt index 4b4a9ba2064..79b4224d375 100644 --- a/question/upgrade.txt +++ b/question/upgrade.txt @@ -1,5 +1,22 @@ This files describes API changes for code that uses the question API. +=== 2.7 === + +1) Changes to class question_bank_view: + + Filters, including $recurse and $showhidden, are now implemented as + pluggable question_bank_search_condition classes. + + Therefore $recurse and $showhidden are no longer passed to the following functions: + protected function display_options [deprecated, use display_options_form()] + protected function build_query_sql [deprecated, use build_query()] + + protected function display_category_form() is deprecated. Use question_bank_search_condition_category + + protected function display_category_form_checkbox deprecated use html_writer::checkbox and separate Javascript + +To add filters, local plugins can now implement the function local_[pluginname]_get_question_bank_search_conditions, + === 2.6 === 1) Modules using the question bank MUST now declare their use of it with the xxx_supports() diff --git a/question/yui/build/moodle-question-searchform/moodle-question-searchform-debug.js b/question/yui/build/moodle-question-searchform/moodle-question-searchform-debug.js new file mode 100644 index 00000000000..31daad66a60 --- /dev/null +++ b/question/yui/build/moodle-question-searchform/moodle-question-searchform-debug.js @@ -0,0 +1,21 @@ + +YUI.add('moodle-question-searchform', function(Y, NAME) { + var SELECTORS = { + OPTIONS: '.searchoptions' + }, + NS; + + M.question = M.question || {}; + NS = M.question.searchform = {}; + + NS.init = function(args) { + Y.delegate('change', this.option_changed, Y.config.doc, SELECTORS.OPTIONS, this); + }; + + NS.option_changed = function(e) { + e.target.getDOMNode().form.submit(); + }; + +}, '@VERSION@', {"requires": ["base", "node"]}); + + diff --git a/question/yui/build/moodle-question-searchform/moodle-question-searchform-min.js b/question/yui/build/moodle-question-searchform/moodle-question-searchform-min.js new file mode 100644 index 00000000000..d47140881a6 --- /dev/null +++ b/question/yui/build/moodle-question-searchform/moodle-question-searchform-min.js @@ -0,0 +1 @@ +YUI.add("moodle-question-searchform",function(e,t){var n={OPTIONS:".searchoptions"},r;M.question=M.question||{},r=M.question.searchform={},r.init=function(t){r.showmore=t.showmore,r.showless=t.showless,r.advsearch=e.one("#advancedsearch"),r.chkshowhideadvsearch=e.one("#showhideadvsearch"),r.chkshowhideadvsearch.on("click",this.advancedsearch_click),t.showadv?r.chkshowhideadvsearch.setHTML(r.showless):r.advsearch.hide(),e.delegate("change",this.option_changed,e.config.doc,n.OPTIONS,this)},r.option_changed=function(e){e.target.getDOMNode().form.submit()},r.advancedsearch_click=function(t){r.advsearch.toggleView(),r.advsearch.getAttribute("hidden")||r.advsearch.getStyle("display")=="none"?(r.chkshowhideadvsearch.setHTML(r.showmore),e.one("#showadv").set("value",0)):(r.chkshowhideadvsearch.setHTML(r.showless),e.one("#showadv").set("value",1))}},"@VERSION@",{requires:["base","node"]})}; diff --git a/question/yui/build/moodle-question-searchform/moodle-question-searchform.js b/question/yui/build/moodle-question-searchform/moodle-question-searchform.js new file mode 100644 index 00000000000..77dbe0d0eef --- /dev/null +++ b/question/yui/build/moodle-question-searchform/moodle-question-searchform.js @@ -0,0 +1,19 @@ + +YUI.add('moodle-question-searchform', function(Y, NAME) { + var SELECTORS = { + OPTIONS: '.searchoptions' + }, + NS; + + M.question = M.question || {}; + NS = M.question.searchform = {}; + + NS.init = function(args) { + Y.delegate('change', this.option_changed, Y.config.doc, SELECTORS.OPTIONS, this); + }; + + NS.option_changed = function(e) { + e.target.getDOMNode().form.submit(); + }; + +}, '@VERSION@', {"requires": ["base", "node"]}); diff --git a/question/yui/src/searchform/build.json b/question/yui/src/searchform/build.json new file mode 100644 index 00000000000..e40e0e955ce --- /dev/null +++ b/question/yui/src/searchform/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-question-searchform", + "builds": { + "moodle-question-searchform": { + "jsfiles": [ + "searchform.js" + ] + } + } +} diff --git a/question/yui/src/searchform/js/searchform.js b/question/yui/src/searchform/js/searchform.js new file mode 100644 index 00000000000..80e8ad8d983 --- /dev/null +++ b/question/yui/src/searchform/js/searchform.js @@ -0,0 +1,17 @@ + + var SELECTORS = { + OPTIONS: '.searchoptions' + }, + NS; + + M.question = M.question || {}; + NS = M.question.searchform = {}; + + NS.init = function() { + Y.delegate('change', this.option_changed, Y.config.doc, SELECTORS.OPTIONS, this); + }; + + NS.option_changed = function(e) { + e.target.getDOMNode().form.submit(); + }; + diff --git a/question/yui/src/searchform/meta/searchform.json b/question/yui/src/searchform/meta/searchform.json new file mode 100644 index 00000000000..e75822b6d45 --- /dev/null +++ b/question/yui/src/searchform/meta/searchform.json @@ -0,0 +1,8 @@ +{ + "moodle-question-searchform": { + "requires": [ + "base", + "node" + ] + } +}