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_tagquestion;
  18  
  19  use core\output\datafilter;
  20  use core_question\local\bank\condition;
  21  
  22  /**
  23   * Question bank search class to allow searching/filtering by tags on a question.
  24   *
  25   * @package   qbank_tagquestion
  26   * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
  27   * @author    2021 Safat Shahin <safatshahin@catalyst-au.net>
  28   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  class tag_condition extends condition {
  31  
  32      /** @var int The default filter type */
  33      const JOINTYPE_DEFAULT = datafilter::JOINTYPE_ALL;
  34  
  35      /** @var array Contexts to be used. */
  36      protected $contexts = [];
  37  
  38      /** @var array List of IDs for tags that have been selected in the form. */
  39      protected $selectedtagids = [];
  40  
  41      /**
  42       * Tag condition constructor. It uses the qbank object and initialises all the its required information
  43       * to be passed as a part of condition to get the questions.
  44       *
  45       * @param null $qbank qbank view
  46       */
  47      public function __construct($qbank = null) {
  48          if (is_null($qbank)) {
  49              return;
  50          }
  51          parent::__construct($qbank);
  52          $cat = $qbank->get_pagevars('cat');
  53          if (is_array($cat)) {
  54              foreach ($cat as $value) {
  55                  [, $contextid] = explode(',', $value);
  56                  $catcontext = \context::instance_by_id($contextid);
  57                  $this->contexts[] = $catcontext;
  58              }
  59          } else {
  60              [, $contextid] = explode(',', $qbank->get_pagevars('cat'));
  61              $catcontext = \context::instance_by_id($contextid);
  62              $this->contexts[] = $catcontext;
  63          }
  64          $thiscontext = $qbank->get_most_specific_context();
  65          $this->contexts[] = $thiscontext;
  66          $this->selectedtagids = $this->filter->values ?? [];
  67      }
  68  
  69      public static function get_condition_key() {
  70          return 'qtagids';
  71      }
  72  
  73      /**
  74       * Print HTML to display the list of tags to filter by.
  75       */
  76      public function display_options() {
  77          global $PAGE;
  78  
  79          $tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $this->contexts);
  80          $tagoptions = array_map(function($tag) {
  81              return [
  82                  'id' => $tag->id,
  83                  'name' => $tag->name,
  84                  'selected' => in_array($tag->id, $this->selectedtagids)
  85              ];
  86          }, array_values($tags));
  87          $context = [
  88              'tagoptions' => $tagoptions
  89          ];
  90  
  91          return $PAGE->get_renderer('qbank_tagquestion')->render_tag_condition($context);
  92      }
  93  
  94      /**
  95       * Build query from filter value
  96       *
  97       * @param array $filter filter properties
  98       * @return array where sql and params
  99       */
 100      public static function build_query_from_filter(array $filter): array {
 101          global $DB;
 102  
 103          // Remove empty string.
 104          $filter['values'] = array_filter($filter['values']);
 105  
 106          $selectedtagids = $filter['values'] ?? [];
 107  
 108          $params = [];
 109          $where = '';
 110          $jointype = $filter['jointype'] ?? self::JOINTYPE_DEFAULT;
 111          // If some tags have been selected then we need to filter
 112          // the question list by the selected tags.
 113          if ($selectedtagids) {
 114              // We treat each additional tag as an AND condition rather than
 115              // an OR condition.
 116              //
 117              // For example, if the user filters by the tags "foo" and "bar" then
 118              // we reduce the question list to questions that are tagged with both
 119              // "foo" AND "bar". Any question that does not have ALL of the specified
 120              // tags will be omitted.
 121              $equal = !($jointype === datafilter::JOINTYPE_NONE);
 122              [$tagsql, $tagparams] = $DB->get_in_or_equal($selectedtagids, SQL_PARAMS_NAMED, 'param', $equal);
 123              $tagparams['tagcount'] = count($selectedtagids);
 124              $tagparams['questionitemtype'] = 'question';
 125              $tagparams['questioncomponent'] = 'core_question';
 126              $params = $tagparams;
 127              $where = "q.id IN (SELECT ti.itemid
 128                                         FROM {tag_instance} ti
 129                                        WHERE ti.itemtype = :questionitemtype
 130                                              AND ti.component = :questioncomponent
 131                                              AND ti.tagid {$tagsql}
 132                                     GROUP BY ti.itemid ";
 133              if ($jointype === datafilter::JOINTYPE_ALL) {
 134                  $where .= "HAVING COUNT(itemid) = :tagcount ";
 135              }
 136              $where .= ") ";
 137  
 138          }
 139  
 140          return [$where, $params];
 141      }
 142  
 143  
 144      public function get_title() {
 145          return get_string('tag', 'tag');
 146      }
 147  
 148      public function get_filter_class() {
 149          return null;
 150      }
 151  
 152      public function allow_custom() {
 153          return false;
 154      }
 155  
 156      public function get_initial_values() {
 157          $tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $this->contexts);
 158          $values = [];
 159          foreach ($tags as $tag) {
 160              $values[] = [
 161                  'value' => $tag->id,
 162                  'title' => html_entity_decode($tag->name),
 163                  'selected' => in_array($tag->id, $this->selectedtagids)
 164              ];
 165          }
 166          return $values;
 167      }
 168  }