Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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   * Base class for search engines.
  19   *
  20   * All search engines must extend this class.
  21   *
  22   * @package   core_search
  23   * @copyright 2015 Daniel Neis
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  namespace core_search;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**
  32   * Base class for search engines.
  33   *
  34   * All search engines must extend this class.
  35   *
  36   * @package   core_search
  37   * @copyright 2015 Daniel Neis
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  abstract class engine {
  41  
  42      /**
  43       * The search engine configuration.
  44       *
  45       * @var \stdClass
  46       */
  47      protected $config = null;
  48  
  49      /**
  50       * Last executed query error, if there was any.
  51       * @var string
  52       */
  53      protected $queryerror = null;
  54  
  55      /**
  56       * @var array Internal cache.
  57       */
  58      protected $cachedareas = array();
  59  
  60      /**
  61       * @var array Internal cache.
  62       */
  63      protected $cachedcourses = array();
  64  
  65      /**
  66       * User data required to show their fullnames. Indexed by userid.
  67       *
  68       * @var \stdClass[]
  69       */
  70      protected static $cachedusers = array();
  71  
  72      /**
  73       * @var string Frankenstyle plugin name.
  74       */
  75      protected $pluginname = null;
  76  
  77      /**
  78       * @var bool If true, should skip schema validity check when checking the search engine is ready
  79       */
  80      protected $skipschemacheck = false;
  81  
  82      /**
  83       * Initialises the search engine configuration.
  84       *
  85       * Search engine availability should be checked separately.
  86       *
  87       * The alternate configuration option is only used to construct a special second copy of the
  88       * search engine object, as described in {@see has_alternate_configuration}.
  89       *
  90       * @param bool $alternateconfiguration If true, use alternate configuration settings
  91       * @return void
  92       */
  93      public function __construct(bool $alternateconfiguration = false) {
  94  
  95          $classname = get_class($this);
  96          if (strpos($classname, '\\') === false) {
  97              throw new \coding_exception('"' . $classname . '" class should specify its component namespace and it should be named engine.');
  98          } else if (strpos($classname, '_') === false) {
  99              throw new \coding_exception('"' . $classname . '" class namespace should be its frankenstyle name');
 100          }
 101  
 102          // This is search_xxxx config.
 103          $this->pluginname = substr($classname, 0, strpos($classname, '\\'));
 104          if ($config = get_config($this->pluginname)) {
 105              $this->config = $config;
 106          } else {
 107              $this->config = new stdClass();
 108          }
 109  
 110          // For alternate configuration, automatically replace normal configuration values with
 111          // those beginning with 'alternate'.
 112          if ($alternateconfiguration) {
 113              foreach ((array)$this->config as $key => $value) {
 114                  if (preg_match('~^alternate(.*)$~', $key, $matches)) {
 115                      $this->config->{$matches[1]} = $value;
 116                  }
 117              }
 118          }
 119  
 120          // Flag just in case engine needs to know it is using the alternate configuration.
 121          $this->config->alternateconfiguration = $alternateconfiguration;
 122      }
 123  
 124      /**
 125       * Returns a course instance checking internal caching.
 126       *
 127       * @param int $courseid
 128       * @return stdClass
 129       */
 130      protected function get_course($courseid) {
 131          if (!empty($this->cachedcourses[$courseid])) {
 132              return $this->cachedcourses[$courseid];
 133          }
 134  
 135          // No need to clone, only read.
 136          $this->cachedcourses[$courseid] = get_course($courseid, false);
 137  
 138          return $this->cachedcourses[$courseid];
 139      }
 140  
 141      /**
 142       * Returns user data checking the internal static cache.
 143       *
 144       * Including here the minimum required user information as this may grow big.
 145       *
 146       * @param int $userid
 147       * @return stdClass
 148       */
 149      public function get_user($userid) {
 150          global $DB;
 151  
 152          if (empty(self::$cachedusers[$userid])) {
 153              $userfieldsapi = \core_user\fields::for_name();
 154              $fields = $userfieldsapi->get_sql('', false, '', '', false)->selects;
 155              self::$cachedusers[$userid] = $DB->get_record('user', array('id' => $userid), 'id, ' . $fields);
 156          }
 157          return self::$cachedusers[$userid];
 158      }
 159  
 160      /**
 161       * Clears the users cache.
 162       *
 163       * @return null
 164       */
 165      public static function clear_users_cache() {
 166          self::$cachedusers = [];
 167      }
 168  
 169      /**
 170       * Returns a search instance of the specified area checking internal caching.
 171       *
 172       * @param string $areaid Area id
 173       * @return \core_search\base
 174       */
 175      protected function get_search_area($areaid) {
 176  
 177          if (isset($this->cachedareas[$areaid]) && $this->cachedareas[$areaid] === false) {
 178              // We already checked that area and it is not available.
 179              return false;
 180          }
 181  
 182          if (!isset($this->cachedareas[$areaid])) {
 183              // First result that matches this area.
 184  
 185              $this->cachedareas[$areaid] = \core_search\manager::get_search_area($areaid);
 186              if ($this->cachedareas[$areaid] === false) {
 187                  // The area does not exist or it is not available any more.
 188  
 189                  $this->cachedareas[$areaid] = false;
 190                  return false;
 191              }
 192  
 193              if (!$this->cachedareas[$areaid]->is_enabled()) {
 194                  // We skip the area if it is not enabled.
 195  
 196                  // Marking it as false so next time we don' need to check it again.
 197                  $this->cachedareas[$areaid] = false;
 198  
 199                  return false;
 200              }
 201          }
 202  
 203          return $this->cachedareas[$areaid];
 204      }
 205  
 206      /**
 207       * Returns a document instance prepared to be rendered.
 208       *
 209       * @param \core_search\base $searcharea
 210       * @param array $docdata
 211       * @return \core_search\document
 212       */
 213      protected function to_document(\core_search\base $searcharea, $docdata) {
 214  
 215          list($componentname, $areaname) = \core_search\manager::extract_areaid_parts($docdata['areaid']);
 216          $doc = \core_search\document_factory::instance($docdata['itemid'], $componentname, $areaname, $this);
 217          $doc->set_data_from_engine($docdata);
 218          $doc->set_doc_url($searcharea->get_doc_url($doc));
 219          $doc->set_context_url($searcharea->get_context_url($doc));
 220          $doc->set_doc_icon($searcharea->get_doc_icon($doc));
 221  
 222          // Uses the internal caches to get required data needed to render the document later.
 223          $course = $this->get_course($doc->get('courseid'));
 224          $doc->set_extra('coursefullname', $course->fullname);
 225  
 226          if ($doc->is_set('userid')) {
 227              $user = $this->get_user($doc->get('userid'));
 228              $doc->set_extra('userfullname', fullname($user));
 229          }
 230  
 231          return $doc;
 232      }
 233  
 234      /**
 235       * Loop through given iterator of search documents
 236       * and and have the search engine back end add them
 237       * to the index.
 238       *
 239       * @param \iterator $iterator the iterator of documents to index
 240       * @param base $searcharea the area for the documents to index
 241       * @param array $options document indexing options
 242       * @return array Processed document counts
 243       */
 244      public function add_documents($iterator, $searcharea, $options) {
 245          $numrecords = 0;
 246          $numdocs = 0;
 247          $numdocsignored = 0;
 248          $numbatches = 0;
 249          $lastindexeddoc = 0;
 250          $firstindexeddoc = 0;
 251          $partial = false;
 252          $lastprogress = manager::get_current_time();
 253  
 254          $batchmode = $this->supports_add_document_batch();
 255          $currentbatch = [];
 256  
 257          foreach ($iterator as $document) {
 258              // Stop if we have exceeded the time limit (and there are still more items). Always
 259              // do at least one second's worth of documents otherwise it will never make progress.
 260              if ($lastindexeddoc !== $firstindexeddoc &&
 261                      !empty($options['stopat']) && manager::get_current_time() >= $options['stopat']) {
 262                  $partial = true;
 263                  break;
 264              }
 265  
 266              if (!$document instanceof \core_search\document) {
 267                  continue;
 268              }
 269  
 270              if (isset($options['lastindexedtime']) && $options['lastindexedtime'] == 0) {
 271                  // If we have never indexed this area before, it must be new.
 272                  $document->set_is_new(true);
 273              }
 274  
 275              if ($options['indexfiles']) {
 276                  // Attach files if we are indexing.
 277                  $searcharea->attach_files($document);
 278              }
 279  
 280              if ($batchmode && strlen($document->get('content')) <= $this->get_batch_max_content()) {
 281                  $currentbatch[] = $document;
 282                  if (count($currentbatch) >= $this->get_batch_max_documents()) {
 283                      [$processed, $failed, $batches] = $this->add_document_batch($currentbatch, $options['indexfiles']);
 284                      $numdocs += $processed;
 285                      $numdocsignored += $failed;
 286                      $numbatches += $batches;
 287                      $currentbatch = [];
 288                  }
 289              } else {
 290                  if ($this->add_document($document, $options['indexfiles'])) {
 291                      $numdocs++;
 292                  } else {
 293                      $numdocsignored++;
 294                  }
 295                  $numbatches++;
 296              }
 297  
 298              $lastindexeddoc = $document->get('modified');
 299              if (!$firstindexeddoc) {
 300                  $firstindexeddoc = $lastindexeddoc;
 301              }
 302              $numrecords++;
 303  
 304              // If indexing the area takes a long time, periodically output progress information.
 305              if (isset($options['progress'])) {
 306                  $now = manager::get_current_time();
 307                  if ($now - $lastprogress >= manager::DISPLAY_INDEXING_PROGRESS_EVERY) {
 308                      $lastprogress = $now;
 309                      // The first date format is the same used in cron_trace_time_and_memory().
 310                      $options['progress']->output(date('H:i:s', (int)$now) . ': Done to ' . userdate(
 311                              $lastindexeddoc, get_string('strftimedatetimeshort', 'langconfig')), 1);
 312                  }
 313              }
 314          }
 315  
 316          // Add remaining documents from batch.
 317          if ($batchmode && $currentbatch) {
 318              [$processed, $failed, $batches] = $this->add_document_batch($currentbatch, $options['indexfiles']);
 319              $numdocs += $processed;
 320              $numdocsignored += $failed;
 321              $numbatches += $batches;
 322          }
 323  
 324          return [$numrecords, $numdocs, $numdocsignored, $lastindexeddoc, $partial, $numbatches];
 325      }
 326  
 327      /**
 328       * Returns the plugin name.
 329       *
 330       * @return string Frankenstyle plugin name.
 331       */
 332      public function get_plugin_name() {
 333          return $this->pluginname;
 334      }
 335  
 336      /**
 337       * Gets the document class used by this search engine.
 338       *
 339       * Search engines can overwrite \core_search\document with \search_ENGINENAME\document class.
 340       *
 341       * Looks for a document class in the current search engine namespace, falling back to \core_search\document.
 342  
 343       * Publicly available because search areas do not have access to the engine details,
 344       * \core_search\document_factory accesses this function.
 345       *
 346       * @return string
 347       */
 348      public function get_document_classname() {
 349          $classname = $this->pluginname . '\\document';
 350          if (!class_exists($classname)) {
 351              $classname = '\\core_search\\document';
 352          }
 353          return $classname;
 354      }
 355  
 356      /**
 357       * Run any pre-indexing operations.
 358       *
 359       * Should be overwritten if the search engine needs to do any pre index preparation.
 360       *
 361       * @param bool $fullindex True if a full index will be performed
 362       * @return void
 363       */
 364      public function index_starting($fullindex = false) {
 365          // Nothing by default.
 366      }
 367  
 368      /**
 369       * Run any post indexing operations.
 370       *
 371       * Should be overwritten if the search engine needs to do any post index cleanup.
 372       *
 373       * @param int $numdocs The number of documents that were added to the index
 374       * @param bool $fullindex True if a full index was performed
 375       * @return void
 376       */
 377      public function index_complete($numdocs = 0, $fullindex = false) {
 378          // Nothing by default.
 379      }
 380  
 381      /**
 382       * Do anything that may need to be done before an area is indexed.
 383       *
 384       * @param \core_search\base $searcharea The search area that was complete
 385       * @param bool $fullindex True if a full index is being performed
 386       * @return void
 387       */
 388      public function area_index_starting($searcharea, $fullindex = false) {
 389          // Nothing by default.
 390      }
 391  
 392      /**
 393       * Do any area cleanup needed, and do anything to confirm contents.
 394       *
 395       * Return false to prevent the search area completed time and stats from being updated.
 396       *
 397       * @param \core_search\base $searcharea The search area that was complete
 398       * @param int $numdocs The number of documents that were added to the index
 399       * @param bool $fullindex True if a full index is being performed
 400       * @return bool True means that data is considered indexed
 401       */
 402      public function area_index_complete($searcharea, $numdocs = 0, $fullindex = false) {
 403          return true;
 404      }
 405  
 406      /**
 407       * Optimizes the search engine.
 408       *
 409       * Should be overwritten if the search engine can optimize its contents.
 410       *
 411       * @return void
 412       */
 413      public function optimize() {
 414          // Nothing by default.
 415          mtrace('The ' . get_string('pluginname', $this->get_plugin_name()) .
 416                  ' search engine does not require automatic optimization.');
 417      }
 418  
 419      /**
 420       * Does the system satisfy all the requirements.
 421       *
 422       * Should be overwritten if the search engine has any system dependencies
 423       * that needs to be checked.
 424       *
 425       * @return bool
 426       */
 427      public function is_installed() {
 428          return true;
 429      }
 430  
 431      /**
 432       * Returns any error reported by the search engine when executing the provided query.
 433       *
 434       * It should be called from static::execute_query when an exception is triggered.
 435       *
 436       * @return string
 437       */
 438      public function get_query_error() {
 439          return $this->queryerror;
 440      }
 441  
 442      /**
 443       * Returns the total number of documents available for the most recent call to execute_query.
 444       *
 445       * This can be an estimate, but should get more accurate the higher the limited passed to execute_query is.
 446       * To do that, the engine can use (actual result returned count + count of unchecked documents), or
 447       * (total possible docs - docs that have been checked and rejected).
 448       *
 449       * Engine can limit to manager::MAX_RESULTS if there is cost to determining more.
 450       * If this cannot be computed in a reasonable way, manager::MAX_RESULTS may be returned.
 451       *
 452       * @return int
 453       */
 454      abstract public function get_query_total_count();
 455  
 456      /**
 457       * Return true if file indexing is supported and enabled. False otherwise.
 458       *
 459       * @return bool
 460       */
 461      public function file_indexing_enabled() {
 462          return false;
 463      }
 464  
 465      /**
 466       * Clears the current query error value.
 467       *
 468       * @return void
 469       */
 470      public function clear_query_error() {
 471          $this->queryerror = null;
 472      }
 473  
 474      /**
 475       * Is the server ready to use?
 476       *
 477       * This should also check that the search engine configuration is ok.
 478       *
 479       * If the function $this->should_skip_schema_check() returns true, then this function may leave
 480       * out time-consuming checks that the schema is valid. (This allows for improved performance on
 481       * critical pages such as the main search form.)
 482       *
 483       * @return true|string Returns true if all good or an error string.
 484       */
 485      abstract function is_server_ready();
 486  
 487      /**
 488       * Tells the search engine to skip any time-consuming checks that it might do as part of the
 489       * is_server_ready function, and only carry out a basic check that it can contact the server.
 490       *
 491       * This setting is not remembered and applies only to the current request.
 492       *
 493       * @since Moodle 3.5
 494       * @param bool $skip True to skip the checks, false to start checking again
 495       */
 496      public function skip_schema_check($skip = true) {
 497          $this->skipschemacheck = $skip;
 498      }
 499  
 500      /**
 501       * For use by subclasses. The engine can call this inside is_server_ready to check whether it
 502       * should skip time-consuming schema checks.
 503       *
 504       * @since Moodle 3.5
 505       * @return bool True if schema checks should be skipped
 506       */
 507      protected function should_skip_schema_check() {
 508          return $this->skipschemacheck;
 509      }
 510  
 511      /**
 512       * Adds a document to the search engine.
 513       *
 514       * @param document $document
 515       * @param bool     $fileindexing True if file indexing is to be used
 516       * @return bool    False if the file was skipped or failed, true on success
 517       */
 518      abstract function add_document($document, $fileindexing = false);
 519  
 520      /**
 521       * Adds multiple documents to the search engine.
 522       *
 523       * It should return the number successfully processed, and the number of batches they were
 524       * processed in (for example if you add 100 documents and there is an error processing one of
 525       * those documents, and it took 4 batches, it would return [99, 1, 4]).
 526       *
 527       * If the engine implements this, it should return true to {@see supports_add_document_batch}.
 528       *
 529       * The system will only call this function with up to {@see get_batch_max_documents} documents,
 530       * and each document in the batch will have content no larger than specified by
 531       * {@see get_batch_max_content}.
 532       *
 533       * @param document[] $documents Documents to add
 534       * @param bool $fileindexing True if file indexing is to be used
 535       * @return int[] Array of three elements, successfully processed, failed processed, batch count
 536       */
 537      public function add_document_batch(array $documents, bool $fileindexing = false): array {
 538          throw new \coding_exception('add_document_batch not supported by this engine');
 539      }
 540  
 541      /**
 542       * Executes the query on the engine.
 543       *
 544       * Implementations of this function should check user context array to limit the results to contexts where the
 545       * user have access. They should also limit the owneruserid field to manger::NO_OWNER_ID or the current user's id.
 546       * Engines must use area->check_access() to confirm user access.
 547       *
 548       * Engines should reasonably attempt to fill up to limit with valid results if they are available.
 549       *
 550       * The $filters object may include the following fields (optional except q):
 551       * - q: value of main search field; results should include this text
 552       * - title: if included, title must match this search
 553       * - areaids: array of search area id strings (only these areas will be searched)
 554       * - courseids: array of course ids (only these courses will be searched)
 555       * - groupids: array of group ids (only results specifically from these groupids will be
 556       *   searched) - this option will be ignored if the search engine doesn't support groups
 557       *
 558       * The $accessinfo parameter has two different values (for historical compatibility). If the
 559       * engine returns false to supports_group_filtering then it is an array of user contexts, or
 560       * true if the user can access all contexts. (This parameter used to be called $usercontexts.)
 561       * If the engine returns true to supports_group_filtering then it will be an object containing
 562       * these fields:
 563       * - everything (true if admin is searching with no restrictions)
 564       * - usercontexts (same as above)
 565       * - separategroupscontexts (array of context ids where separate groups are used)
 566       * - visiblegroupscontextsareas (array of subset of those where some areas use visible groups)
 567       * - usergroups (array of relevant group ids that user belongs to)
 568       *
 569       * The engine should apply group restrictions to those contexts listed in the
 570       * 'separategroupscontexts' array. In these contexts, it shouled only include results if the
 571       * groupid is not set, or if the groupid matches one of the values in USER_GROUPS array, or
 572       * if the search area is one of those listed in 'visiblegroupscontextsareas' for that context.
 573       *
 574       * @param \stdClass $filters Query and filters to apply.
 575       * @param \stdClass $accessinfo Information about the contexts the user can access
 576       * @param  int      $limit The maximum number of results to return. If empty, limit to manager::MAX_RESULTS.
 577       * @return \core_search\document[] Results or false if no results
 578       */
 579      public abstract function execute_query($filters, $accessinfo, $limit = 0);
 580  
 581      /**
 582       * Delete all documents.
 583       *
 584       * @param string $areaid To filter by area
 585       * @return void
 586       */
 587      abstract function delete($areaid = null);
 588  
 589      /**
 590       * Deletes information related to a specific context id. This should be used when the context
 591       * itself is deleted from Moodle.
 592       *
 593       * This only deletes information for the specified context - not for any child contexts.
 594       *
 595       * This function is optional; if not supported it will return false and the information will
 596       * not be deleted from the search index.
 597       *
 598       * If an engine implements this function it should also implement delete_index_for_course;
 599       * otherwise, nothing will be deleted when users delete an entire course at once.
 600       *
 601       * @param int $oldcontextid ID of context that has been deleted
 602       * @return bool True if implemented
 603       * @throws \core_search\engine_exception Engines may throw this exception for any problem
 604       */
 605      public function delete_index_for_context(int $oldcontextid) {
 606          return false;
 607      }
 608  
 609      /**
 610       * Deletes information related to a specific course id. This should be used when the course
 611       * itself is deleted from Moodle.
 612       *
 613       * This deletes all information relating to that course from the index, including all child
 614       * contexts.
 615       *
 616       * This function is optional; if not supported it will return false and the information will
 617       * not be deleted from the search index.
 618       *
 619       * If an engine implements this function then, ideally, it should also implement
 620       * delete_index_for_context so that deletion of single activities/blocks also works.
 621       *
 622       * @param int $oldcourseid ID of course that has been deleted
 623       * @return bool True if implemented
 624       * @throws \core_search\engine_exception Engines may throw this exception for any problem
 625       */
 626      public function delete_index_for_course(int $oldcourseid) {
 627          return false;
 628      }
 629  
 630      /**
 631       * Checks that the schema is the latest version. If the version stored in config does not match
 632       * the current, this function will attempt to upgrade the schema.
 633       *
 634       * @return bool|string True if schema is OK, a string if user needs to take action
 635       */
 636      public function check_latest_schema() {
 637          if (empty($this->config->schemaversion)) {
 638              $currentversion = 0;
 639          } else {
 640              $currentversion = $this->config->schemaversion;
 641          }
 642          if ($currentversion < document::SCHEMA_VERSION) {
 643              return $this->update_schema((int)$currentversion, (int)document::SCHEMA_VERSION);
 644          } else {
 645              return true;
 646          }
 647      }
 648  
 649      /**
 650       * Usually called by the engine; marks that the schema has been updated.
 651       *
 652       * @param int $version Records the schema version now applied
 653       */
 654      public function record_applied_schema_version($version) {
 655          set_config('schemaversion', $version, $this->pluginname);
 656      }
 657  
 658      /**
 659       * Requests the search engine to upgrade the schema. The engine should update the schema if
 660       * possible/necessary, and should ensure that record_applied_schema_version is called as a
 661       * result.
 662       *
 663       * If it is not possible to upgrade the schema at the moment, it can do nothing and return; the
 664       * function will be called again next time search is initialised.
 665       *
 666       * The default implementation just returns, with a DEBUG_DEVELOPER warning.
 667       *
 668       * @param int $oldversion Old schema version
 669       * @param int $newversion New schema version
 670       * @return bool|string True if schema is updated successfully, a string if it needs updating manually
 671       */
 672      protected function update_schema($oldversion, $newversion) {
 673          debugging('Unable to update search engine schema: ' . $this->pluginname, DEBUG_DEVELOPER);
 674          return get_string('schemanotupdated', 'search');
 675      }
 676  
 677      /**
 678       * Checks if this search engine supports groups.
 679       *
 680       * Note that returning true to this function causes the parameters to execute_query to be
 681       * passed differently!
 682       *
 683       * In order to implement groups and return true to this function, the search engine should:
 684       *
 685       * 1. Handle the fields ->separategroupscontexts and ->usergroups in the $accessinfo parameter
 686       *    to execute_query (ideally, using these to automatically restrict search results).
 687       * 2. Support the optional groupids parameter in the $filter parameter for execute_query to
 688       *    restrict results to only those where the stored groupid matches the given value.
 689       *
 690       * @return bool True if this engine supports searching by group id field
 691       */
 692      public function supports_group_filtering() {
 693          return false;
 694      }
 695  
 696      /**
 697       * Obtain a list of results orders (and names for them) that are supported by this
 698       * search engine in the given context.
 699       *
 700       * By default, engines sort by relevance only.
 701       *
 702       * @param \context $context Context that the user requested search from
 703       * @return array Array from order name => display text
 704       */
 705      public function get_supported_orders(\context $context) {
 706          return ['relevance' => get_string('order_relevance', 'search')];
 707      }
 708  
 709      /**
 710       * Checks if the search engine supports searching by user.
 711       *
 712       * If it returns true to this function, the search engine should support the 'userids' option
 713       * in the $filters value passed to execute_query(), returning only items where the userid in
 714       * the search document matches one of those user ids.
 715       *
 716       * @return bool True if the search engine supports searching by user
 717       */
 718      public function supports_users() {
 719          return false;
 720      }
 721  
 722      /**
 723       * Checks if the search engine supports adding documents in a batch.
 724       *
 725       * If it returns true to this function, the search engine must implement the add_document_batch
 726       * function.
 727       *
 728       * @return bool True if the search engine supports adding documents in a batch
 729       */
 730      public function supports_add_document_batch(): bool {
 731          return false;
 732      }
 733  
 734      /**
 735       * Gets the maximum number of documents to send together in batch mode.
 736       *
 737       * Only relevant if the engine returns true to {@see supports_add_document_batch}.
 738       *
 739       * Can be overridden by search engine if required.
 740       *
 741       * @var int Number of documents to send together in batch mode, default 100.
 742       */
 743      public function get_batch_max_documents(): int {
 744          return 100;
 745      }
 746  
 747      /**
 748       * Gets the maximum size of document content to be included in a shared batch (if the
 749       * document is bigger then it will be sent on its own; batching does not provide a performance
 750       * improvement for big documents anyway).
 751       *
 752       * Only relevant if the engine returns true to {@see supports_add_document_batch}.
 753       *
 754       * Can be overridden by search engine if required.
 755       *
 756       * @return int Max size in bytes, default 1MB
 757       */
 758      public function get_batch_max_content(): int {
 759          return 1024 * 1024;
 760      }
 761  
 762      /**
 763       * Checks if the search engine has an alternate configuration.
 764       *
 765       * This is used where the same search engine class supports two different configurations,
 766       * which are both shown on the settings screen. The alternate configuration is selected by
 767       * passing 'true' parameter to the constructor.
 768       *
 769       * The feature is used when a different connection is in use for indexing vs. querying
 770       * the search engine.
 771       *
 772       * This function should only return true if the engine supports an alternate configuration
 773       * and the user has filled in the settings. (We do not need to test they are valid, that will
 774       * happen as normal.)
 775       *
 776       * @return bool True if an alternate configuration is defined
 777       */
 778      public function has_alternate_configuration(): bool {
 779          return false;
 780      }
 781  }