[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/question/classes/bank/ -> view.php (source)

   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 = '&amp;quizid=' . $cm->instance;
  87          } else {
  88              $this->quizorcourseid = '&amp;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>&nbsp;'.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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1