Differences Between: [Versions 310 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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', $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body