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] [Versions 39 and 310]

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