Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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  namespace qbank_managecategories;
  18  
  19  use core\output\datafilter;
  20  use core_question\local\bank\condition;
  21  use core_question\local\bank\view;
  22  
  23  /**
  24   * This class controls from which category questions are listed.
  25   *
  26   * @package   qbank_managecategories
  27   * @copyright 2013 Ray Morris
  28   * @author    2021 Safat Shahin <safatshahin@catalyst-au.net>
  29   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class category_condition extends condition {
  32      /** @var \stdClass The course record. */
  33      protected $course;
  34  
  35      /** @var \stdClass The category record. */
  36      protected $category;
  37  
  38      /** @var array of contexts. */
  39      protected $contexts;
  40  
  41      /** @var string categoryID,contextID as used with question_bank_view->display(). */
  42      protected $cat;
  43  
  44      /** @var int The maximum displayed length of the category info. */
  45      public $maxinfolength;
  46  
  47      /** @var bool Include questions in subcategories of the specified category? */
  48      public $includesubcategories;
  49  
  50      /**
  51       * Constructor to initialize the category filter condition.
  52       *
  53       * @param view $qbank qbank view
  54       */
  55      public function __construct(view $qbank = null) {
  56          if (is_null($qbank)) {
  57              return;
  58          }
  59          $this->cat = $qbank->get_pagevars('cat');
  60          $this->contexts = $qbank->contexts->having_one_edit_tab_cap($qbank->get_pagevars('tabname'));
  61          $this->course = $qbank->course;
  62  
  63          [$categoryid, $contextid] = self::validate_category_param($this->cat);
  64          if (is_null($categoryid)) {
  65              return;
  66          }
  67  
  68          $this->category = self::get_category_record($categoryid, $contextid);
  69  
  70          parent::__construct($qbank);
  71          $this->includesubcategories = $this->filter['filteroptions']['includesubcategories'] ?? false;
  72      }
  73  
  74      /**
  75       * Return default category
  76       *
  77       * @return \stdClass default category
  78       */
  79      public function get_default_category(): \stdClass {
  80          if (empty($this->category)) {
  81              return question_get_default_category(\context_course::instance($this->course->id)->id);
  82          }
  83  
  84          return $this->category;
  85      }
  86  
  87      public static function get_condition_key() {
  88          return 'category';
  89      }
  90  
  91      /**
  92       * Returns course id.
  93       *
  94       * @return string Course id.
  95       */
  96      public function get_course_id() {
  97          return $this->course->id;
  98      }
  99  
 100      /**
 101       * Called by question_bank_view to display the GUI for selecting a category
 102       * @deprecated since Moodle 4.3 MDL-72321 - please do not use this function any more.
 103       * @todo Final deprecation on Moodle 4.7 MDL-78090
 104       */
 105      public function display_options() {
 106          debugging('Function display_options() is deprecated, please use filtering objects', DEBUG_DEVELOPER);
 107          global $PAGE;
 108          $displaydata = [];
 109          $catmenu = helper::question_category_options($this->contexts, true, 0,
 110                  true, -1, false);
 111          $displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [],
 112                  array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
 113          $displaydata['categorydesc'] = '';
 114          if ($this->category) {
 115              $displaydata['categorydesc'] = $this->print_category_info($this->category);
 116          }
 117          return $PAGE->get_renderer('qbank_managecategories')->render_category_condition($displaydata);
 118      }
 119  
 120      /**
 121       * Displays the recursion checkbox GUI.
 122       * question_bank_view places this within the section that is hidden by default
 123       * @deprecated since Moodle 4.3 MDL-72321 - please do not use this function any more.
 124       * @todo Final deprecation on Moodle 4.7 MDL-78090
 125       */
 126      public function display_options_adv() {
 127          debugging('Function display_options_adv() is deprecated, please use filtering objects', DEBUG_DEVELOPER);
 128          global $PAGE;
 129          $displaydata = [];
 130          if ($this->recurse) {
 131              $displaydata['checked'] = 'checked';
 132          }
 133          return $PAGE->get_renderer('qbank_managecategories')->render_category_condition_advanced($displaydata);
 134      }
 135  
 136      /**
 137       * Display the drop down to select the category.
 138       *
 139       * @param array $contexts of contexts that can be accessed from here.
 140       * @param \moodle_url $pageurl the URL of this page.
 141       * @param string $current 'categoryID,contextID'.
 142       * @deprecated since Moodle 4.3
 143       * @todo Final deprecation on Moodle 4.7 MDL-78090
 144       */
 145      protected function display_category_form($contexts, $pageurl, $current) {
 146          debugging(
 147              'Function display_category_form() is deprecated, please use the core_question renderer instead.',
 148              DEBUG_DEVELOPER
 149          );
 150          echo \html_writer::start_div('choosecategory');
 151          $catmenu = question_category_options($contexts, true, 0, true, -1, false);
 152          echo \html_writer::label(get_string('selectacategory', 'question'), 'id_selectacategory', true, ["class" => "mr-1"]);
 153          echo \html_writer::select($catmenu, 'category', $current, [],
 154                  array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
 155          echo \html_writer::end_div() . "\n";
 156      }
 157  
 158      /**
 159       * Print the text if category id not available.
 160       * @deprecated since Moodle 4.3
 161       * @todo Final deprecation in Moodle 4.7 MDL-78090
 162       */
 163      public static function print_choose_category_message(): void {
 164          debugging(
 165              'Function print_choose_category_message() is deprecated, please use ' .
 166                  'qbank_managecategories/choose_category template instead.',
 167              DEBUG_DEVELOPER
 168          );
 169          global $OUTPUT;
 170          echo $OUTPUT->render_from_template('qbank_managecategories/choose_category', []);
 171      }
 172  
 173      /**
 174       * Look up the category record based on category ID and context
 175       * @param string $categoryandcontext categoryID,contextID as used with question_bank_view->display()
 176       * @return \stdClass The category record
 177       * @deprecated since Moodle 4.3
 178       * @todo Final deprecation in Moodle 4.7 MDL-78090
 179       */
 180      public static function get_current_category($categoryandcontext) {
 181          debugging('Function get_current_category() is deprecated. Please do not use it anymore.', DEBUG_DEVELOPER);
 182          global $DB, $OUTPUT;
 183          [$categoryid, $contextid] = explode(',', $categoryandcontext);
 184          if (!$categoryid) {
 185              self::print_choose_category_message();
 186              return false;
 187          }
 188  
 189          if (!$category = $DB->get_record('question_categories', ['id' => $categoryid, 'contextid' => $contextid])) {
 190              echo $OUTPUT->box_start('generalbox questionbank');
 191              echo $OUTPUT->notification('Category not found!');
 192              echo $OUTPUT->box_end();
 193              return false;
 194          }
 195  
 196          return $category;
 197      }
 198  
 199      /**
 200       * Return category and context ID from compound parameter.
 201       *
 202       * @param string $categoryandcontext Comma-separated list of category and context IDs.
 203       * @return int[]|null[]
 204       */
 205      public static function validate_category_param(string $categoryandcontext): array {
 206          [$categoryid, $contextid] = explode(',', $categoryandcontext);
 207          if (!$categoryid) {
 208              return [null, null];
 209          }
 210          return [clean_param($categoryid, PARAM_INT), clean_param($contextid, PARAM_INT)];
 211      }
 212  
 213      /**
 214       * Fetch the question category record matching the provided category and context IDs.
 215       *
 216       * @param int $categoryid
 217       * @param int $contextid
 218       * @return \stdClass
 219       * @throws \dml_exception
 220       */
 221      public static function get_category_record($categoryid, $contextid): \stdClass {
 222          global $DB;
 223          return $DB->get_record('question_categories',
 224                  ['id' => $categoryid, 'contextid' => $contextid],
 225                  '*',
 226                  MUST_EXIST);
 227      }
 228  
 229      /**
 230       * Print the category description
 231       * @param \stdClass $category the category information form the database.
 232       * @deprecated since Moodle 4.3 MDL-72321 - please do not use this function any more.
 233       * @todo Final deprecation on Moodle 4.7 MDL-78090
 234       */
 235      protected function print_category_info($category): string {
 236          debugging('Function print_category_info() is deprecated. Please do not use it anymore', DEBUG_DEVELOPER);
 237          $formatoptions = new \stdClass();
 238          $formatoptions->noclean = true;
 239          $formatoptions->overflowdiv = true;
 240          if (isset($this->maxinfolength)) {
 241              return shorten_text(format_text($category->info, $category->infoformat, $formatoptions, $this->course->id),
 242                      $this->maxinfolength);
 243          }
 244  
 245          return format_text($category->info, $category->infoformat, $formatoptions, $this->course->id);
 246      }
 247  
 248      public static function build_query_from_filter(array $filter): array {
 249          global $DB;
 250          $recursive = false;
 251          if (isset($filter['filteroptions']['includesubcategories'])) {
 252              $recursive = (bool)$filter['filteroptions']['includesubcategories'];
 253          }
 254  
 255          // Sub categories.
 256          if ($recursive) {
 257              $categories = $filter['values'];
 258              $categoriesandsubcategories = [];
 259              foreach ($categories as $categoryid) {
 260                  $categoriesandsubcategories += question_categorylist($categoryid);
 261              }
 262          } else {
 263              $categoriesandsubcategories = $filter['values'];
 264          }
 265  
 266          $jointype = $filter['jointype'] ?? self::JOINTYPE_DEFAULT;
 267          $equal = !($jointype === datafilter::JOINTYPE_NONE);
 268          [$insql, $params] = $DB->get_in_or_equal($categoriesandsubcategories, SQL_PARAMS_NAMED, 'cat', $equal);
 269          $where = 'qbe.questioncategoryid ' . $insql;
 270          return [$where, $params];
 271      }
 272  
 273      public function get_title() {
 274          return get_string('category', 'core_question');
 275      }
 276  
 277      public function get_filter_class() {
 278          return 'qbank_managecategories/datafilter/filtertypes/categories';
 279      }
 280  
 281      public function allow_custom() {
 282          return false;
 283      }
 284  
 285      public function allow_multiple() {
 286          return false;
 287      }
 288  
 289      public function allow_empty() {
 290          return false;
 291      }
 292  
 293      public function get_join_list(): array {
 294          return [
 295                  datafilter::JOINTYPE_ANY,
 296          ];
 297      }
 298  
 299      public function get_initial_values() {
 300          $catmenu = helper::question_category_options($this->contexts, true, 0, true, -1, false);
 301          $values = [];
 302          foreach ($catmenu as $menu) {
 303              foreach ($menu as $heading => $catlist) {
 304                  $values[] = (object) [
 305                      // Add a list item for each question category context. This will serve as a "heading" within the list
 306                      // and we will use CSS to disable pointer events so it cannot be selected.
 307                      'value' => '',
 308                      'title' => $heading,
 309                      'disabled' => true,
 310                  ];
 311                  foreach ($catlist as $key => $value) {
 312                      $values[] = (object) [
 313                          // Remove contextid from value.
 314                          'value' => str_contains($key, ',') ? substr($key, 0, strpos($key, ',')) : $key,
 315                          'title' => $value,
 316                          'selected' => ($key === $this->cat),
 317                      ];
 318                  }
 319              }
 320          }
 321          return $values;
 322      }
 323  
 324      public function get_filteroptions(): \stdClass {
 325          return (object)[
 326              'includesubcategories' => $this->includesubcategories,
 327          ];
 328      }
 329  
 330      public function is_required(): bool {
 331          return true;
 332      }
 333  }