[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 18 namespace core_question\bank; 19 20 /** 21 * Functions used to show question editing interface 22 * 23 * @package moodlecore 24 * @subpackage questionbank 25 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 30 /** 31 * This class prints a view of the question bank, including 32 * + Some controls to allow users to to select what is displayed. 33 * + A list of questions as a table. 34 * + Further controls to do things with the questions. 35 * 36 * This class gives a basic view, and provides plenty of hooks where subclasses 37 * can override parts of the display. 38 * 39 * The list of questions presented as a table is generated by creating a list of 40 * core_question\bank\column objects, one for each 'column' to be displayed. These 41 * manage 42 * + outputting the contents of that column, given a $question object, but also 43 * + generating the right fragments of SQL to ensure the necessary data is present, 44 * and sorted in the right order. 45 * + outputting table headers. 46 * 47 * @copyright 2009 Tim Hunt 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 49 */ 50 class view { 51 const MAX_SORTS = 3; 52 53 protected $baseurl; 54 protected $editquestionurl; 55 protected $quizorcourseid; 56 protected $contexts; 57 protected $cm; 58 protected $course; 59 protected $visiblecolumns; 60 protected $extrarows; 61 protected $requiredcolumns; 62 protected $sort; 63 protected $lastchangedid; 64 protected $countsql; 65 protected $loadsql; 66 protected $sqlparams; 67 /** @var array of \core_question\bank\search\condition objects. */ 68 protected $searchconditions = array(); 69 70 /** 71 * Constructor 72 * @param question_edit_contexts $contexts 73 * @param moodle_url $pageurl 74 * @param object $course course settings 75 * @param object $cm (optional) activity settings. 76 */ 77 public function __construct($contexts, $pageurl, $course, $cm = null) { 78 global $CFG, $PAGE; 79 80 $this->contexts = $contexts; 81 $this->baseurl = $pageurl; 82 $this->course = $course; 83 $this->cm = $cm; 84 85 if (!empty($cm) && $cm->modname == 'quiz') { 86 $this->quizorcourseid = '&quizid=' . $cm->instance; 87 } else { 88 $this->quizorcourseid = '&courseid=' .$this->course->id; 89 } 90 91 // Create the url of the new question page to forward to. 92 $returnurl = $pageurl->out_as_local_url(false); 93 $this->editquestionurl = new \moodle_url('/question/question.php', 94 array('returnurl' => $returnurl)); 95 if ($cm !== null) { 96 $this->editquestionurl->param('cmid', $cm->id); 97 } else { 98 $this->editquestionurl->param('courseid', $this->course->id); 99 } 100 101 $this->lastchangedid = optional_param('lastchanged', 0, PARAM_INT); 102 103 $this->init_columns($this->wanted_columns(), $this->heading_column()); 104 $this->init_sort(); 105 $this->init_search_conditions($this->contexts, $this->course, $this->cm); 106 } 107 108 /** 109 * Initialize search conditions from plugins 110 * local_*_get_question_bank_search_conditions() must return an array of 111 * \core_question\bank\search\condition objects. 112 */ 113 protected function init_search_conditions() { 114 $searchplugins = get_plugin_list_with_function('local', 'get_question_bank_search_conditions'); 115 foreach ($searchplugins as $component => $function) { 116 foreach ($function($this) as $searchobject) { 117 $this->add_searchcondition($searchobject); 118 } 119 } 120 } 121 122 protected function wanted_columns() { 123 global $CFG; 124 125 if (empty($CFG->questionbankcolumns)) { 126 $questionbankcolumns = array('checkbox_column', 'question_type_column', 127 'question_name_column', 'edit_action_column', 'copy_action_column', 128 'preview_action_column', 'delete_action_column', 129 'creator_name_column', 130 'modifier_name_column'); 131 } else { 132 $questionbankcolumns = explode(',', $CFG->questionbankcolumns); 133 } 134 if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) { 135 $questionbankcolumns[] = 'question_text_row'; 136 } 137 138 foreach ($questionbankcolumns as $fullname) { 139 if (! class_exists($fullname)) { 140 if (class_exists('core_question\\bank\\' . $fullname)) { 141 $fullname = 'core_question\\bank\\' . $fullname; 142 } else { 143 throw new \coding_exception("No such class exists: $fullname"); 144 } 145 } 146 $this->requiredcolumns[$fullname] = new $fullname($this); 147 } 148 return $this->requiredcolumns; 149 } 150 151 152 /** 153 * Get a column object from its name. 154 * 155 * @param string $columnname. 156 * @return \core_question\bank\column_base. 157 */ 158 protected function get_column_type($columnname) { 159 if (! class_exists($columnname)) { 160 if (class_exists('core_question\\bank\\' . $columnname)) { 161 $columnname = 'core_question\\bank\\' . $columnname; 162 } else { 163 throw new \coding_exception("No such class exists: $columnname"); 164 } 165 } 166 if (empty($this->requiredcolumns[$columnname])) { 167 $this->requiredcolumns[$columnname] = new $columnname($this); 168 } 169 return $this->requiredcolumns[$columnname]; 170 } 171 172 /** 173 * Specify the column heading 174 * 175 * @return string Column name for the heading 176 */ 177 protected function heading_column() { 178 return 'question_bank_question_name_column'; 179 } 180 181 /** 182 * Initializing table columns 183 * 184 * @param array $wanted Collection of column names 185 * @param string $heading The name of column that is set as heading 186 */ 187 protected function init_columns($wanted, $heading = '') { 188 $this->visiblecolumns = array(); 189 $this->extrarows = array(); 190 foreach ($wanted as $column) { 191 if ($column->is_extra_row()) { 192 $this->extrarows[get_class($column)] = $column; 193 } else { 194 $this->visiblecolumns[get_class($column)] = $column; 195 } 196 } 197 if (array_key_exists($heading, $this->requiredcolumns)) { 198 $this->requiredcolumns[$heading]->set_as_heading(); 199 } 200 } 201 202 /** 203 * @param string $colname a column internal name. 204 * @return bool is this column included in the output? 205 */ 206 public function has_column($colname) { 207 return isset($this->visiblecolumns[$colname]); 208 } 209 210 /** 211 * @return int The number of columns in the table. 212 */ 213 public function get_column_count() { 214 return count($this->visiblecolumns); 215 } 216 217 public function get_courseid() { 218 return $this->course->id; 219 } 220 221 protected function init_sort() { 222 $this->init_sort_from_params(); 223 if (empty($this->sort)) { 224 $this->sort = $this->default_sort(); 225 } 226 } 227 228 /** 229 * Deal with a sort name of the form columnname, or colname_subsort by 230 * breaking it up, validating the bits that are presend, and returning them. 231 * If there is no subsort, then $subsort is returned as ''. 232 * @return array array($colname, $subsort). 233 */ 234 protected function parse_subsort($sort) { 235 // Do the parsing. 236 if (strpos($sort, '-') !== false) { 237 list($colname, $subsort) = explode('-', $sort, 2); 238 } else { 239 $colname = $sort; 240 $subsort = ''; 241 } 242 // Validate the column name. 243 $column = $this->get_column_type($colname); 244 if (!isset($column) || !$column->is_sortable()) { 245 for ($i = 1; $i <= self::MAX_SORTS; $i++) { 246 $this->baseurl->remove_params('qbs' . $i); 247 } 248 throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $colname); 249 } 250 // Validate the subsort, if present. 251 if ($subsort) { 252 $subsorts = $column->is_sortable(); 253 if (!is_array($subsorts) || !isset($subsorts[$subsort])) { 254 throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $sort); 255 } 256 } 257 return array($colname, $subsort); 258 } 259 260 protected function init_sort_from_params() { 261 $this->sort = array(); 262 for ($i = 1; $i <= self::MAX_SORTS; $i++) { 263 if (!$sort = optional_param('qbs' . $i, '', PARAM_TEXT)) { 264 break; 265 } 266 // Work out the appropriate order. 267 $order = 1; 268 if ($sort[0] == '-') { 269 $order = -1; 270 $sort = substr($sort, 1); 271 if (!$sort) { 272 break; 273 } 274 } 275 // Deal with subsorts. 276 list($colname, $subsort) = $this->parse_subsort($sort); 277 $this->requiredcolumns[$colname] = $this->get_column_type($colname); 278 $this->sort[$sort] = $order; 279 } 280 } 281 282 protected function sort_to_params($sorts) { 283 $params = array(); 284 $i = 0; 285 foreach ($sorts as $sort => $order) { 286 $i += 1; 287 if ($order < 0) { 288 $sort = '-' . $sort; 289 } 290 $params['qbs' . $i] = $sort; 291 } 292 return $params; 293 } 294 295 protected function default_sort() { 296 return array('core_question\bank\question_type_column' => 1, 'core_question\bank\question_name_column' => 1); 297 } 298 299 /** 300 * @param $sort a column or column_subsort name. 301 * @return int the current sort order for this column -1, 0, 1 302 */ 303 public function get_primary_sort_order($sort) { 304 $order = reset($this->sort); 305 $primarysort = key($this->sort); 306 if ($sort == $primarysort) { 307 return $order; 308 } else { 309 return 0; 310 } 311 } 312 313 /** 314 * Get a URL to redisplay the page with a new sort for the question bank. 315 * @param string $sort the column, or column_subsort to sort on. 316 * @param bool $newsortreverse whether to sort in reverse order. 317 * @return string The new URL. 318 */ 319 public function new_sort_url($sort, $newsortreverse) { 320 if ($newsortreverse) { 321 $order = -1; 322 } else { 323 $order = 1; 324 } 325 // Tricky code to add the new sort at the start, removing it from where it was before, if it was present. 326 $newsort = array_reverse($this->sort); 327 if (isset($newsort[$sort])) { 328 unset($newsort[$sort]); 329 } 330 $newsort[$sort] = $order; 331 $newsort = array_reverse($newsort); 332 if (count($newsort) > self::MAX_SORTS) { 333 $newsort = array_slice($newsort, 0, self::MAX_SORTS, true); 334 } 335 return $this->baseurl->out(true, $this->sort_to_params($newsort)); 336 } 337 338 /** 339 * Create the SQL query to retrieve the indicated questions 340 * @param stdClass $category no longer used. 341 * @param bool $recurse no longer used. 342 * @param bool $showhidden no longer used. 343 * @deprecated since Moodle 2.7 MDL-40313. 344 * @see build_query() 345 * @see \core_question\bank\search\condition 346 * @todo MDL-41978 This will be deleted in Moodle 2.8 347 */ 348 protected function build_query_sql($category, $recurse, $showhidden) { 349 debugging('build_query_sql() is deprecated, please use \core_question\bank\view::build_query() and ' . 350 '\core_question\bank\search\condition classes instead.', DEBUG_DEVELOPER); 351 self::build_query(); 352 } 353 354 /** 355 * Create the SQL query to retrieve the indicated questions, based on 356 * \core_question\bank\search\condition filters. 357 */ 358 protected function build_query() { 359 global $DB; 360 361 // Get the required tables and fields. 362 $joins = array(); 363 $fields = array('q.hidden', 'q.category'); 364 foreach ($this->requiredcolumns as $column) { 365 $extrajoins = $column->get_extra_joins(); 366 foreach ($extrajoins as $prefix => $join) { 367 if (isset($joins[$prefix]) && $joins[$prefix] != $join) { 368 throw new \coding_exception('Join ' . $join . ' conflicts with previous join ' . $joins[$prefix]); 369 } 370 $joins[$prefix] = $join; 371 } 372 $fields = array_merge($fields, $column->get_required_fields()); 373 } 374 $fields = array_unique($fields); 375 376 // Build the order by clause. 377 $sorts = array(); 378 foreach ($this->sort as $sort => $order) { 379 list($colname, $subsort) = $this->parse_subsort($sort); 380 $sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort); 381 } 382 383 // Build the where clause. 384 $tests = array('q.parent = 0'); 385 $this->sqlparams = array(); 386 foreach ($this->searchconditions as $searchcondition) { 387 if ($searchcondition->where()) { 388 $tests[] = '((' . $searchcondition->where() .'))'; 389 } 390 if ($searchcondition->params()) { 391 $this->sqlparams = array_merge($this->sqlparams, $searchcondition->params()); 392 } 393 } 394 // Build the SQL. 395 $sql = ' FROM {question} q ' . implode(' ', $joins); 396 $sql .= ' WHERE ' . implode(' AND ', $tests); 397 $this->countsql = 'SELECT count(1)' . $sql; 398 $this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts); 399 } 400 401 protected function get_question_count() { 402 global $DB; 403 return $DB->count_records_sql($this->countsql, $this->sqlparams); 404 } 405 406 protected function load_page_questions($page, $perpage) { 407 global $DB; 408 $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, $page * $perpage, $perpage); 409 if (!$questions->valid()) { 410 // No questions on this page. Reset to page 0. 411 $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, 0, $perpage); 412 } 413 return $questions; 414 } 415 416 public function base_url() { 417 return $this->baseurl; 418 } 419 420 public function edit_question_url($questionid) { 421 return $this->editquestionurl->out(true, array('id' => $questionid)); 422 } 423 424 /** 425 * Get the URL for duplicating a given question. 426 * @param int $questionid the question id. 427 * @return moodle_url the URL. 428 */ 429 public function copy_question_url($questionid) { 430 return $this->editquestionurl->out(true, array('id' => $questionid, 'makecopy' => 1)); 431 } 432 433 /** 434 * Get the context we are displaying the question bank for. 435 * @return context context object. 436 */ 437 public function get_most_specific_context() { 438 return $this->contexts->lowest(); 439 } 440 441 /** 442 * Get the URL to preview a question. 443 * @param stdClass $questiondata the data defining the question. 444 * @return moodle_url the URL. 445 */ 446 public function preview_question_url($questiondata) { 447 return question_preview_url($questiondata->id, null, null, null, null, 448 $this->get_most_specific_context()); 449 } 450 451 /** 452 * Shows the question bank editing interface. 453 * 454 * The function also processes a number of actions: 455 * 456 * Actions affecting the question pool: 457 * move Moves a question to a different category 458 * deleteselected Deletes the selected questions from the category 459 * Other actions: 460 * category Chooses the category 461 * displayoptions Sets display options 462 */ 463 public function display($tabname, $page, $perpage, $cat, 464 $recurse, $showhidden, $showquestiontext) { 465 global $PAGE, $OUTPUT; 466 467 if ($this->process_actions_needing_ui()) { 468 return; 469 } 470 $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); 471 // Category selection form. 472 echo $OUTPUT->heading(get_string('questionbank', 'question'), 2); 473 array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden)); 474 array_unshift($this->searchconditions, new \core_question\bank\search\category_condition( 475 $cat, $recurse, $editcontexts, $this->baseurl, $this->course)); 476 $this->display_options_form($showquestiontext); 477 478 // Continues with list of questions. 479 $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname), 480 $this->baseurl, $cat, $this->cm, 481 null, $page, $perpage, $showhidden, $showquestiontext, 482 $this->contexts->having_cap('moodle/question:add')); 483 } 484 485 protected function print_choose_category_message($categoryandcontext) { 486 echo "<p style=\"text-align:center;\"><b>"; 487 print_string('selectcategoryabove', 'question'); 488 echo "</b></p>"; 489 } 490 491 protected function get_current_category($categoryandcontext) { 492 global $DB, $OUTPUT; 493 list($categoryid, $contextid) = explode(',', $categoryandcontext); 494 if (!$categoryid) { 495 $this->print_choose_category_message($categoryandcontext); 496 return false; 497 } 498 499 if (!$category = $DB->get_record('question_categories', 500 array('id' => $categoryid, 'contextid' => $contextid))) { 501 echo $OUTPUT->box_start('generalbox questionbank'); 502 echo $OUTPUT->notification('Category not found!'); 503 echo $OUTPUT->box_end(); 504 return false; 505 } 506 507 return $category; 508 } 509 510 /** 511 * prints category information 512 * @param stdClass $category the category row from the database. 513 * @deprecated since Moodle 2.7 MDL-40313. 514 * @see \core_question\bank\search\condition 515 * @todo MDL-41978 This will be deleted in Moodle 2.8 516 */ 517 protected function print_category_info($category) { 518 $formatoptions = new \stdClass(); 519 $formatoptions->noclean = true; 520 $formatoptions->overflowdiv = true; 521 echo '<div class="boxaligncenter">'; 522 echo format_text($category->info, $category->infoformat, $formatoptions, $this->course->id); 523 echo "</div>\n"; 524 } 525 526 /** 527 * Prints a form to choose categories 528 * @deprecated since Moodle 2.7 MDL-40313. 529 * @see \core_question\bank\search\condition 530 * @todo MDL-41978 This will be deleted in Moodle 2.8 531 */ 532 protected function display_category_form($contexts, $pageurl, $current) { 533 global $OUTPUT; 534 535 debugging('display_category_form() is deprecated, please use ' . 536 '\core_question\bank\search\condition instead.', DEBUG_DEVELOPER); 537 // Get all the existing categories now. 538 echo '<div class="choosecategory">'; 539 $catmenu = question_category_options($contexts, false, 0, true); 540 541 $select = new \single_select($this->baseurl, 'category', $catmenu, $current, null, 'catmenu'); 542 $select->set_label(get_string('selectacategory', 'question')); 543 echo $OUTPUT->render($select); 544 echo "</div>\n"; 545 } 546 547 /** 548 * Display the options form. 549 * @param bool $recurse no longer used. 550 * @param bool $showhidden no longer used. 551 * @param bool $showquestiontext whether to show the question text. 552 * @deprecated since Moodle 2.7 MDL-40313. 553 * @see display_options_form 554 * @todo MDL-41978 This will be deleted in Moodle 2.8 555 * @see \core_question\bank\search\condition 556 */ 557 protected function display_options($recurse, $showhidden, $showquestiontext) { 558 debugging('display_options() is deprecated, please use display_options_form instead.', DEBUG_DEVELOPER); 559 return $this->display_options_form($showquestiontext); 560 } 561 562 /** 563 * Print a single option checkbox. 564 * @deprecated since Moodle 2.7 MDL-40313. 565 * @see \core_question\bank\search\condition 566 * @see html_writer::checkbox 567 * @todo MDL-41978 This will be deleted in Moodle 2.8 568 */ 569 protected function display_category_form_checkbox($name, $value, $label) { 570 debugging('display_category_form_checkbox() is deprecated, ' . 571 'please use \core_question\bank\search\condition instead.', DEBUG_DEVELOPER); 572 echo '<div><input type="hidden" id="' . $name . '_off" name="' . $name . '" value="0" />'; 573 echo '<input type="checkbox" id="' . $name . '_on" name="' . $name . '" value="1"'; 574 if ($value) { 575 echo ' checked="checked"'; 576 } 577 echo ' onchange="getElementById(\'displayoptions\').submit(); return true;" />'; 578 echo '<label for="' . $name . '_on">' . $label . '</label>'; 579 echo "</div>\n"; 580 } 581 582 /** 583 * Display the form with options for which questions are displayed and how they are displayed. 584 * @param bool $showquestiontext Display the text of the question within the list. 585 * @param string $path path to the script displaying this page. 586 * @param bool $showtextoption whether to include the 'Show question text' checkbox. 587 */ 588 protected function display_options_form($showquestiontext, $scriptpath = '/question/edit.php', 589 $showtextoption = true) { 590 global $PAGE; 591 592 echo \html_writer::start_tag('form', array('method' => 'get', 593 'action' => new \moodle_url($scriptpath), 'id' => 'displayoptions')); 594 echo \html_writer::start_div(); 595 echo \html_writer::input_hidden_params($this->baseurl, array('recurse', 'showhidden', 'qbshowtext')); 596 597 foreach ($this->searchconditions as $searchcondition) { 598 echo $searchcondition->display_options($this); 599 } 600 if ($showtextoption) { 601 $this->display_showtext_checkbox($showquestiontext); 602 } 603 $this->display_advanced_search_form(); 604 $go = \html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'))); 605 echo \html_writer::tag('noscript', \html_writer::div($go), array('class' => 'inline')); 606 echo \html_writer::end_div(); 607 echo \html_writer::end_tag('form'); 608 $PAGE->requires->yui_module('moodle-question-searchform', 'M.question.searchform.init'); 609 } 610 611 /** 612 * Print the "advanced" UI elements for the form to select which questions. Hidden by default. 613 */ 614 protected function display_advanced_search_form() { 615 print_collapsible_region_start('', 'advancedsearch', get_string('advancedsearchoptions', 'question'), 616 'question_bank_advanced_search'); 617 foreach ($this->searchconditions as $searchcondition) { 618 echo $searchcondition->display_options_adv($this); 619 } 620 print_collapsible_region_end(); 621 } 622 623 /** 624 * Display the checkbox UI for toggling the display of the question text in the list. 625 * @param bool $showquestiontext the current or default value for whether to display the text. 626 */ 627 protected function display_showtext_checkbox($showquestiontext) { 628 echo '<div>'; 629 echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'qbshowtext', 630 'value' => 0, 'id' => 'qbshowtext_off')); 631 echo \html_writer::checkbox('qbshowtext', '1', $showquestiontext, get_string('showquestiontext', 'question'), 632 array('id' => 'qbshowtext_on', 'class' => 'searchoptions')); 633 echo "</div>\n"; 634 } 635 636 protected function create_new_question_form($category, $canadd) { 637 global $CFG; 638 echo '<div class="createnewquestion">'; 639 if ($canadd) { 640 create_new_question_button($category->id, $this->editquestionurl->params(), 641 get_string('createnewquestion', 'question')); 642 } else { 643 print_string('nopermissionadd', 'question'); 644 } 645 echo '</div>'; 646 } 647 648 /** 649 * Prints the table of questions in a category with interactions 650 * 651 * @param array $contexts Not used! 652 * @param moodle_url $pageurl The URL to reload this page. 653 * @param string $categoryandcontext 'categoryID,contextID'. 654 * @param stdClass $cm Not used! 655 * @param bool $recurse Whether to include subcategories. 656 * @param int $page The number of the page to be displayed 657 * @param int $perpage Number of questions to show per page 658 * @param bool $showhidden whether deleted questions should be displayed. 659 * @param bool $showquestiontext whether the text of each question should be shown in the list. Deprecated. 660 * @param array $addcontexts contexts where the user is allowed to add new questions. 661 */ 662 protected function display_question_list($contexts, $pageurl, $categoryandcontext, 663 $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false, 664 $showquestiontext = false, $addcontexts = array()) { 665 global $CFG, $DB, $OUTPUT; 666 667 // This function can be moderately slow with large question counts and may time out. 668 // We probably do not want to raise it to unlimited, so randomly picking 5 minutes. 669 // Note: We do not call this in the loop because quiz ob_ captures this function (see raise() PHP doc). 670 \core_php_time_limit::raise(300); 671 672 $category = $this->get_current_category($categoryandcontext); 673 674 $strselectall = get_string('selectall'); 675 $strselectnone = get_string('deselectall'); 676 677 list($categoryid, $contextid) = explode(',', $categoryandcontext); 678 $catcontext = \context::instance_by_id($contextid); 679 680 $canadd = has_capability('moodle/question:add', $catcontext); 681 682 $this->create_new_question_form($category, $canadd); 683 684 $this->build_query(); 685 $totalnumber = $this->get_question_count(); 686 if ($totalnumber == 0) { 687 return; 688 } 689 $questions = $this->load_page_questions($page, $perpage); 690 691 echo '<div class="categorypagingbarcontainer">'; 692 $pageingurl = new \moodle_url('edit.php'); 693 $r = $pageingurl->params($pageurl->params()); 694 $pagingbar = new \paging_bar($totalnumber, $page, $perpage, $pageingurl); 695 $pagingbar->pagevar = 'qpage'; 696 echo $OUTPUT->render($pagingbar); 697 echo '</div>'; 698 699 echo '<form method="post" action="edit.php">'; 700 echo '<fieldset class="invisiblefieldset" style="display: block;">'; 701 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />'; 702 echo \html_writer::input_hidden_params($this->baseurl); 703 704 echo '<div class="categoryquestionscontainer">'; 705 $this->start_table(); 706 $rowcount = 0; 707 foreach ($questions as $question) { 708 $this->print_table_row($question, $rowcount); 709 $rowcount += 1; 710 } 711 $this->end_table(); 712 echo "</div>\n"; 713 714 echo '<div class="categorypagingbarcontainer pagingbottom">'; 715 echo $OUTPUT->render($pagingbar); 716 if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) { 717 if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) { 718 $url = new \moodle_url('edit.php', array_merge($pageurl->params(), 719 array('qperpage' => MAXIMUM_QUESTIONS_PER_PAGE))); 720 if ($totalnumber > MAXIMUM_QUESTIONS_PER_PAGE) { 721 $showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', MAXIMUM_QUESTIONS_PER_PAGE).'</a>'; 722 } else { 723 $showall = '<a href="'.$url.'">'.get_string('showall', 'moodle', $totalnumber).'</a>'; 724 } 725 } else { 726 $url = new \moodle_url('edit.php', array_merge($pageurl->params(), 727 array('qperpage' => DEFAULT_QUESTIONS_PER_PAGE))); 728 $showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>'; 729 } 730 echo "<div class='paging'>{$showall}</div>"; 731 } 732 echo '</div>'; 733 734 $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts); 735 736 echo '</fieldset>'; 737 echo "</form>\n"; 738 } 739 740 /** 741 * Display the controls at the bottom of the list of questions. 742 * @param int $totalnumber Total number of questions that might be shown (if it was not for paging). 743 * @param bool $recurse Whether to include subcategories. 744 * @param stdClass $category The question_category row from the database. 745 * @param context $catcontext The context of the category being displayed. 746 * @param array $addcontexts contexts where the user is allowed to add new questions. 747 */ 748 protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) { 749 $caneditall = has_capability('moodle/question:editall', $catcontext); 750 $canuseall = has_capability('moodle/question:useall', $catcontext); 751 $canmoveall = has_capability('moodle/question:moveall', $catcontext); 752 753 echo '<div class="modulespecificbuttonscontainer">'; 754 if ($caneditall || $canmoveall || $canuseall) { 755 echo '<strong> '.get_string('withselected', 'question').':</strong><br />'; 756 757 // Print delete and move selected question. 758 if ($caneditall) { 759 echo '<input type="submit" name="deleteselected" value="' . get_string('delete') . "\" />\n"; 760 } 761 762 if ($canmoveall && count($addcontexts)) { 763 echo '<input type="submit" name="move" value="' . get_string('moveto', 'question') . "\" />\n"; 764 question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}"); 765 } 766 } 767 echo "</div>\n"; 768 } 769 770 protected function start_table() { 771 echo '<table id="categoryquestions">' . "\n"; 772 echo "<thead>\n"; 773 $this->print_table_headers(); 774 echo "</thead>\n"; 775 echo "<tbody>\n"; 776 } 777 778 protected function end_table() { 779 echo "</tbody>\n"; 780 echo "</table>\n"; 781 } 782 783 protected function print_table_headers() { 784 echo "<tr>\n"; 785 foreach ($this->visiblecolumns as $column) { 786 $column->display_header(); 787 } 788 echo "</tr>\n"; 789 } 790 791 protected function get_row_classes($question, $rowcount) { 792 $classes = array(); 793 if ($question->hidden) { 794 $classes[] = 'dimmed_text'; 795 } 796 if ($question->id == $this->lastchangedid) { 797 $classes[] = 'highlight'; 798 } 799 $classes[] = 'r' . ($rowcount % 2); 800 return $classes; 801 } 802 803 protected function print_table_row($question, $rowcount) { 804 $rowclasses = implode(' ', $this->get_row_classes($question, $rowcount)); 805 if ($rowclasses) { 806 echo '<tr class="' . $rowclasses . '">' . "\n"; 807 } else { 808 echo "<tr>\n"; 809 } 810 foreach ($this->visiblecolumns as $column) { 811 $column->display($question, $rowclasses); 812 } 813 echo "</tr>\n"; 814 foreach ($this->extrarows as $row) { 815 $row->display($question, $rowclasses); 816 } 817 } 818 819 public function process_actions() { 820 global $CFG, $DB; 821 // Now, check for commands on this page and modify variables as necessary. 822 if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { 823 // Move selected questions to new category. 824 $category = required_param('category', PARAM_SEQUENCE); 825 list($tocategoryid, $contextid) = explode(',', $category); 826 if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) { 827 print_error('cannotfindcate', 'question'); 828 } 829 $tocontext = \context::instance_by_id($contextid); 830 require_capability('moodle/question:add', $tocontext); 831 $rawdata = (array) data_submitted(); 832 $questionids = array(); 833 foreach ($rawdata as $key => $value) { // Parse input for question ids. 834 if (preg_match('!^q([0-9]+)$!', $key, $matches)) { 835 $key = $matches[1]; 836 $questionids[] = $key; 837 } 838 } 839 if ($questionids) { 840 list($usql, $params) = $DB->get_in_or_equal($questionids); 841 $sql = ""; 842 $questions = $DB->get_records_sql(" 843 SELECT q.*, c.contextid 844 FROM {question} q 845 JOIN {question_categories} c ON c.id = q.category 846 WHERE q.id {$usql}", $params); 847 foreach ($questions as $question) { 848 question_require_capability_on($question, 'move'); 849 } 850 question_move_questions_to_category($questionids, $tocategory->id); 851 redirect($this->baseurl->out(false, 852 array('category' => "{$tocategoryid},{$contextid}"))); 853 } 854 } 855 856 if (optional_param('deleteselected', false, PARAM_BOOL)) { // Delete selected questions from the category. 857 // If teacher has already confirmed the action. 858 if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { 859 $deleteselected = required_param('deleteselected', PARAM_RAW); 860 if ($confirm == md5($deleteselected)) { 861 if ($questionlist = explode(',', $deleteselected)) { 862 // For each question either hide it if it is in use or delete it. 863 foreach ($questionlist as $questionid) { 864 $questionid = (int)$questionid; 865 question_require_capability_on($questionid, 'edit'); 866 if (questions_in_use(array($questionid))) { 867 $DB->set_field('question', 'hidden', 1, array('id' => $questionid)); 868 } else { 869 question_delete_question($questionid); 870 } 871 } 872 } 873 redirect($this->baseurl); 874 } else { 875 print_error('invalidconfirm', 'question'); 876 } 877 } 878 } 879 880 // Unhide a question. 881 if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) { 882 question_require_capability_on($unhide, 'edit'); 883 $DB->set_field('question', 'hidden', 0, array('id' => $unhide)); 884 885 // Purge these questions from the cache. 886 \question_bank::notify_question_edited($unhide); 887 888 redirect($this->baseurl); 889 } 890 } 891 892 public function process_actions_needing_ui() { 893 global $DB, $OUTPUT; 894 if (optional_param('deleteselected', false, PARAM_BOOL)) { 895 // Make a list of all the questions that are selected. 896 $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. 897 $questionlist = ''; // comma separated list of ids of questions to be deleted 898 $questionnames = ''; // string with names of questions separated by <br /> with 899 // an asterix in front of those that are in use 900 $inuse = false; // set to true if at least one of the questions is in use 901 foreach ($rawquestions as $key => $value) { // Parse input for question ids. 902 if (preg_match('!^q([0-9]+)$!', $key, $matches)) { 903 $key = $matches[1]; 904 $questionlist .= $key.','; 905 question_require_capability_on($key, 'edit'); 906 if (questions_in_use(array($key))) { 907 $questionnames .= '* '; 908 $inuse = true; 909 } 910 $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . '<br />'; 911 } 912 } 913 if (!$questionlist) { // No questions were selected. 914 redirect($this->baseurl); 915 } 916 $questionlist = rtrim($questionlist, ','); 917 918 // Add an explanation about questions in use. 919 if ($inuse) { 920 $questionnames .= '<br />'.get_string('questionsinuse', 'question'); 921 } 922 $baseurl = new \moodle_url('edit.php', $this->baseurl->params()); 923 $deleteurl = new \moodle_url($baseurl, array('deleteselected' => $questionlist, 'confirm' => md5($questionlist), 924 'sesskey' => sesskey())); 925 926 $continue = new \single_button($deleteurl, get_string('delete'), 'post'); 927 echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $continue, $baseurl); 928 929 return true; 930 } 931 } 932 933 /** 934 * Add another search control to this view. 935 * @param \core_question\bank\search\condition $searchcondition the condition to add. 936 */ 937 public function add_searchcondition($searchcondition) { 938 $this->searchconditions[] = $searchcondition; 939 } 940 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |