Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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   * Search area base class for areas working at module level.
  19   *
  20   * @package    core_search
  21   * @copyright  2015 David Monllao {@link http://www.davidmonllao.com}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_search;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Base implementation for search areas working at module level.
  31   *
  32   * Even if the search area works at multiple levels, if module is one of these levels
  33   * it should extend this class, as this class provides helper methods for module level search management.
  34   *
  35   * @package    core_search
  36   * @copyright  2015 David Monllao {@link http://www.davidmonllao.com}
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  abstract class base_mod extends base {
  40  
  41      /**
  42       * The context levels the search area is working on.
  43       *
  44       * This can be overwriten by the search area if it works at multiple
  45       * levels.
  46       *
  47       * @var array
  48       */
  49      protected static $levels = [CONTEXT_MODULE];
  50  
  51      /**
  52       * Returns the module name.
  53       *
  54       * @return string
  55       */
  56      protected function get_module_name() {
  57          return substr($this->componentname, 4);
  58      }
  59  
  60      /**
  61       * Gets the course module for the required instanceid + modulename.
  62       *
  63       * The returned data depends on the logged user, when calling this through
  64       * self::get_document the admin user is used so everything would be returned.
  65       *
  66       * No need more internal caching here, modinfo is already cached.
  67       *
  68       * @throws \dml_missing_record_exception
  69       * @param string $modulename The module name
  70       * @param int $instanceid Module instance id (depends on the module)
  71       * @param int $courseid Helps speeding up things
  72       * @return \cm_info
  73       */
  74      protected function get_cm($modulename, $instanceid, $courseid) {
  75          $modinfo = get_fast_modinfo($courseid);
  76  
  77          // Hopefully not many, they are indexed by cmid.
  78          $instances = $modinfo->get_instances_of($modulename);
  79          foreach ($instances as $cminfo) {
  80              if ($cminfo->instance == $instanceid) {
  81                  return $cminfo;
  82              }
  83          }
  84  
  85          // Nothing found.
  86          throw new \dml_missing_record_exception($modulename);
  87      }
  88  
  89      /**
  90       * Helper function that gets SQL useful for restricting a search query given a passed-in
  91       * context.
  92       *
  93       * The SQL returned will be zero or more JOIN statements, surrounded by whitespace, which act
  94       * as restrictions on the query based on the rows in a module table.
  95       *
  96       * You can pass in a null or system context, which will both return an empty string and no
  97       * params.
  98       *
  99       * Returns an array with two nulls if there can be no results for the activity within this
 100       * context (e.g. it is a block context).
 101       *
 102       * If named parameters are used, these will be named gcrs0, gcrs1, etc. The table aliases used
 103       * in SQL also all begin with gcrs, to avoid conflicts.
 104       *
 105       * @param \context|null $context Context to restrict the query
 106       * @param string $modname Name of module e.g. 'forum'
 107       * @param string $modtable Alias of table containing module id
 108       * @param int $paramtype Type of SQL parameters to use (default question mark)
 109       * @return array Array with SQL and parameters; both null if no need to query
 110       * @throws \coding_exception If called with invalid params
 111       */
 112      protected function get_context_restriction_sql(\context $context = null, $modname, $modtable,
 113              $paramtype = SQL_PARAMS_QM) {
 114          global $DB;
 115  
 116          if (!$context) {
 117              return ['', []];
 118          }
 119  
 120          switch ($paramtype) {
 121              case SQL_PARAMS_QM:
 122                  $param1 = '?';
 123                  $param2 = '?';
 124                  $param3 = '?';
 125                  $key1 = 0;
 126                  $key2 = 1;
 127                  $key3 = 2;
 128                  break;
 129              case SQL_PARAMS_NAMED:
 130                  $param1 = ':gcrs0';
 131                  $param2 = ':gcrs1';
 132                  $param3 = ':gcrs2';
 133                  $key1 = 'gcrs0';
 134                  $key2 = 'gcrs1';
 135                  $key3 = 'gcrs2';
 136                  break;
 137              default:
 138                  throw new \coding_exception('Unexpected $paramtype: ' . $paramtype);
 139          }
 140  
 141          $params = [];
 142          switch ($context->contextlevel) {
 143              case CONTEXT_SYSTEM:
 144                  $sql = '';
 145                  break;
 146  
 147              case CONTEXT_COURSECAT:
 148                  // Find all activities of this type within the specified category or any
 149                  // sub-category.
 150                  $pathmatch = $DB->sql_like('gcrscc2.path', $DB->sql_concat('gcrscc1.path', $param3));
 151                  $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
 152                                AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param1)
 153                           JOIN {course} gcrsc ON gcrsc.id = gcrscm.course
 154                           JOIN {course_categories} gcrscc1 ON gcrscc1.id = $param2
 155                           JOIN {course_categories} gcrscc2 ON gcrscc2.id = gcrsc.category AND
 156                                (gcrscc2.id = gcrscc1.id OR $pathmatch) ";
 157                  $params[$key1] = $modname;
 158                  $params[$key2] = $context->instanceid;
 159                  // Note: This param is a bit annoying as it obviously never changes, but sql_like
 160                  // throws a debug warning if you pass it anything with quotes in, so it has to be
 161                  // a bound parameter.
 162                  $params[$key3] = '/%';
 163                  break;
 164  
 165              case CONTEXT_COURSE:
 166                  // Find all activities of this type within the course.
 167                  $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
 168                                AND gcrscm.course = $param1
 169                                AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param2) ";
 170                  $params[$key1] = $context->instanceid;
 171                  $params[$key2] = $modname;
 172                  break;
 173  
 174              case CONTEXT_MODULE:
 175                  // Find only the specified activity of this type.
 176                  $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
 177                                AND gcrscm.id = $param1
 178                                AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param2) ";
 179                  $params[$key1] = $context->instanceid;
 180                  $params[$key2] = $modname;
 181                  break;
 182  
 183              case CONTEXT_BLOCK:
 184              case CONTEXT_USER:
 185                  // These contexts cannot contain any activities, so return null.
 186                  return [null, null];
 187  
 188              default:
 189                  throw new \coding_exception('Unexpected contextlevel: ' . $context->contextlevel);
 190          }
 191  
 192          return [$sql, $params];
 193      }
 194  
 195      /**
 196       * This can be used in subclasses to change ordering within the get_contexts_to_reindex
 197       * function.
 198       *
 199       * It returns 2 values:
 200       * - Extra SQL joins (tables course_modules 'cm' and context 'x' already exist).
 201       * - An ORDER BY value which must use aggregate functions, by default 'MAX(cm.added) DESC'.
 202       *
 203       * Note the query already includes a GROUP BY on the context fields, so if your joins result
 204       * in multiple rows, you can use aggregate functions in the ORDER BY. See forum for an example.
 205       *
 206       * @return string[] Array with 2 elements; extra joins for the query, and ORDER BY value
 207       */
 208      protected function get_contexts_to_reindex_extra_sql() {
 209          return ['', 'MAX(cm.added) DESC'];
 210      }
 211  
 212      /**
 213       * Gets a list of all contexts to reindex when reindexing this search area.
 214       *
 215       * For modules, the default is to return all contexts for modules of that type, in order of
 216       * time added (most recent first).
 217       *
 218       * @return \Iterator Iterator of contexts to reindex
 219       * @throws \moodle_exception If any DB error
 220       */
 221      public function get_contexts_to_reindex() {
 222          global $DB;
 223  
 224          list ($extrajoins, $dborder) = $this->get_contexts_to_reindex_extra_sql();
 225          $contexts = [];
 226          $selectcolumns = \context_helper::get_preload_record_columns_sql('x');
 227          $groupbycolumns = '';
 228          foreach (\context_helper::get_preload_record_columns('x') as $column => $thing) {
 229              if ($groupbycolumns !== '') {
 230                  $groupbycolumns .= ',';
 231              }
 232              $groupbycolumns .= $column;
 233          }
 234          $rs = $DB->get_recordset_sql("
 235                  SELECT $selectcolumns
 236                    FROM {course_modules} cm
 237                    JOIN {context} x ON x.instanceid = cm.id AND x.contextlevel = ?
 238                         $extrajoins
 239                   WHERE cm.module = (SELECT id FROM {modules} WHERE name = ?)
 240                GROUP BY $groupbycolumns
 241                ORDER BY $dborder", [CONTEXT_MODULE, $this->get_module_name()]);
 242          return new \core\dml\recordset_walk($rs, function($rec) {
 243              $id = $rec->ctxid;
 244              \context_helper::preload_from_record($rec);
 245              return \context::instance_by_id($id);
 246          });
 247      }
 248  
 249      /**
 250       * Indicates whether this search area may restrict access by group.
 251       *
 252       * This should return true if the search area (sometimes) sets the 'groupid' schema field, and
 253       * false if it never sets that field.
 254       *
 255       * (If this function returns false, but the field is set, then results may be restricted
 256       * unintentionally.)
 257       *
 258       * If this returns true, the search engine will automatically apply group restrictions in some
 259       * cases (by default, where a module is configured to use separate groups). See function
 260       * restrict_cm_access_by_group().
 261       *
 262       * @return bool
 263       */
 264      public function supports_group_restriction() {
 265          return false;
 266      }
 267  
 268      /**
 269       * Checks whether the content of this search area should be restricted by group for a
 270       * specific module. Called at query time.
 271       *
 272       * The default behaviour simply checks if the effective group mode is SEPARATEGROUPS, which
 273       * is probably correct for most cases.
 274       *
 275       * If restricted by group, the search query will (where supported by the engine) filter out
 276       * results for groups the user does not belong to, unless the user has 'access all groups'
 277       * for the activity. This affects only documents which set the 'groupid' field; results with no
 278       * groupid will not be restricted.
 279       *
 280       * Even if you return true to this function, you may still need to do group access checks in
 281       * check_access, because the search engine may not support group restrictions.
 282       *
 283       * @param \cm_info $cm
 284       * @return bool True to restrict by group
 285       */
 286      public function restrict_cm_access_by_group(\cm_info $cm) {
 287          return $cm->effectivegroupmode == SEPARATEGROUPS;
 288      }
 289  
 290      /**
 291       * Returns an icon instance for the document.
 292       *
 293       * @param \core_search\document $doc
 294       * @return \core_search\document_icon
 295       */
 296      public function get_doc_icon(document $doc) : document_icon {
 297          return new document_icon('icon', $this->get_module_name());
 298      }
 299  
 300      /**
 301       * Returns a list of category names associated with the area.
 302       *
 303       * @return array
 304       */
 305      public function get_category_names() {
 306          return [manager::SEARCH_AREA_CATEGORY_COURSE_CONTENT];
 307      }
 308  }