Merge branch 'MDL-68729-master' of https://github.com/sammarshallou/moodle into master

This commit is contained in:
Eloy Lafuente (stronk7) 2020-08-12 01:28:51 +02:00
commit 62a7d2b5de
14 changed files with 342 additions and 26 deletions

View File

@ -549,8 +549,19 @@ if ($hassiteconfig) {
// Search engine selection.
$temp->add(new admin_setting_heading('searchengineheading', new lang_string('searchengine', 'admin'), ''));
$temp->add(new admin_setting_configselect('searchengine',
new lang_string('selectsearchengine', 'admin'), '', 'simpledb', $engines));
$searchengineselect = new admin_setting_configselect('searchengine',
new lang_string('selectsearchengine', 'admin'), '', 'simpledb', $engines);
$searchengineselect->set_validate_function(function(string $value): string {
global $CFG;
// Check nobody's setting the indexing and query-only server to the same one.
if ($CFG->searchenginequeryonly === $value) {
return get_string('searchenginequeryonlysame', 'admin');
} else {
return '';
}
});
$temp->add($searchengineselect);
$temp->add(new admin_setting_heading('searchoptionsheading', new lang_string('searchoptions', 'admin'), ''));
$temp->add(new admin_setting_configcheckbox('searchindexwhendisabled',
new lang_string('searchindexwhendisabled', 'admin'), new lang_string('searchindexwhendisabled_desc', 'admin'),
@ -590,6 +601,43 @@ if ($hassiteconfig) {
new lang_string('searchhideallcategory_desc', 'admin'),
0));
$temp->add(new admin_setting_heading('searchmanagement', new lang_string('searchmanagement', 'admin'),
new lang_string('searchmanagement_desc', 'admin')));
// Get list of search engines including those with alternate settings.
$searchenginequeryonlyselect = new admin_setting_configselect('searchenginequeryonly',
new lang_string('searchenginequeryonly', 'admin'),
new lang_string('searchenginequeryonly_desc', 'admin'), '', function() use($engines) {
$options = ['' => new lang_string('searchenginequeryonly_none', 'admin')];
foreach ($engines as $name => $display) {
$options[$name] = $display;
$classname = '\search_' . $name . '\engine';
$engine = new $classname;
if ($engine->has_alternate_configuration()) {
$options[$name . '-alternate'] =
new lang_string('searchenginealternatesettings', 'admin', $display);
}
}
return $options;
});
$searchenginequeryonlyselect->set_validate_function(function(string $value): string {
global $CFG;
// Check nobody's setting the indexing and query-only server to the same one.
if ($CFG->searchengine === $value) {
return get_string('searchenginequeryonlysame', 'admin');
} else {
return '';
}
});
$temp->add($searchenginequeryonlyselect);
$temp->add(new admin_setting_configcheckbox('searchbannerenable',
new lang_string('searchbannerenable', 'admin'), new lang_string('searchbannerenable_desc', 'admin'),
0));
$temp->add(new admin_setting_confightmleditor('searchbanner',
new lang_string('searchbanner', 'admin'), '', ''));
$ADMIN->add('searchplugins', $temp);
$ADMIN->add('searchplugins', new admin_externalpage('searchareas', new lang_string('searchareas', 'admin'),
new moodle_url('/admin/searchareas.php')));

View File

@ -1107,6 +1107,8 @@ $string['searchallavailablecourses'] = 'Searchable courses';
$string['searchallavailablecourses_off'] = 'Search within enrolled courses only';
$string['searchallavailablecourses_on'] = 'Search within all courses the user can access';
$string['searchallavailablecourses_desc'] = 'In some situations the search engine may not work when searching across a large number of courses. Set to search only enrolled courses if you need to restrict the number of courses searched.';
$string['searchalternatesettings'] = 'Query-only alternate settings';
$string['searchalternatesettings_desc'] = 'If you complete these settings, you can select \'alternate settings\' for this search engine in the query-only search engine option on the \'Manage global search\' page. This is only useful when moving between two search engines of the same type.';
$string['searchdisplay'] = 'Search results display options';
$string['searchenablecategories'] = 'Display results in separate categories';
$string['searchenablecategories_desc'] = 'If enabled, search results will be displayed in separate categories.';
@ -1118,10 +1120,18 @@ $string['searchallavailablecoursesdesc'] = 'If set to search within enrolled cou
$string['searchincludeallcourses'] = 'Include all visible courses';
$string['searchincludeallcourses_desc'] = 'If enabled, search results will include course information (name and summary) of courses which are visible to the user, even if they don\'t have access to the course content.';
$string['searchalldeleted'] = 'All indexed contents have been deleted';
$string['searchbannerenable'] = 'Display search information';
$string['searchbannerenable_desc'] = 'If enabled, the below text will display at the top of the search screen for all users. This can be used to keep users informed while maintenance is being carried out on the search system.';
$string['searchbanner'] = 'Search information';
$string['searchareaenabled'] = 'Search area enabled';
$string['searchareadisabled'] = 'Search area disabled';
$string['searchdeleteindex'] = 'Delete all indexed contents';
$string['searchengine'] = 'Search engine';
$string['searchenginealternatesettings'] = '{$a} (alternate settings)';
$string['searchenginequeryonly'] = 'Query-only search engine';
$string['searchenginequeryonly_desc'] = 'This search engine will be used only for making queries, not indexing. By using this feature you can reindex in a different search engine, while user queries continue to work from this one.';
$string['searchenginequeryonly_none'] = 'None (use main search engine for queries)';
$string['searchenginequeryonlysame'] = 'The query-only search engine and the main search engine cannot be set to the same value.';
$string['searchindexactions'] = 'Index actions';
$string['searchindexdeleted'] = 'Index deleted';
$string['searchindextime'] = 'Indexing time limit';
@ -1131,6 +1141,8 @@ $string['searchindexwhendisabled'] = 'Index when disabled';
$string['searchindexwhendisabled_desc'] = 'Allows the scheduled task to build the search index even when search is disabled. This is useful if you want to build the index before the search facility appears to students.';
$string['searchinsettings'] = 'Search in settings';
$string['searchlastrun'] = 'Last run (time, # docs, # records, # ignores)';
$string['searchmanagement'] = 'Search management';
$string['searchmanagement_desc'] = 'These options are useful when making changes on sites with very large search indexes that take a long time to rebuild.';
$string['searchnotavailable'] = 'Search is not available';
$string['searchpartial'] = '(not yet fully indexed)';
$string['searchoptions'] = 'Search options';

View File

@ -3232,14 +3232,22 @@ class admin_setting_configselect extends admin_setting {
public $choices;
/** @var array Array of choices grouped using optgroups */
public $optgroups;
/** @var callable|null Loader function for choices */
protected $choiceloader = null;
/** @var callable|null Validation function */
protected $validatefunction = null;
/**
* Constructor
* Constructor.
*
* If you want to lazy-load the choices, pass a callback function that returns a choice
* array for the $choices parameter.
*
* @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
* @param string $visiblename localised
* @param string $description long localised info
* @param string|int $defaultsetting
* @param array $choices array of $value=>$label for each selection
* @param array|callable|null $choices array of $value=>$label for each selection, or callback
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
// Look for optgroup and single options.
@ -3254,10 +3262,26 @@ class admin_setting_configselect extends admin_setting {
}
}
}
if (is_callable($choices)) {
$this->choiceloader = $choices;
}
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* Sets a validate function.
*
* The callback will be passed one parameter, the new setting value, and should return either
* an empty string '' if the value is OK, or an error message if not.
*
* @param callable|null $validatefunction Validate function or null to clear
* @since Moodle 4.0
*/
public function set_validate_function(?callable $validatefunction = null) {
$this->validatefunction = $validatefunction;
}
/**
* This function may be used in ancestors for lazy loading of choices
*
@ -3267,12 +3291,12 @@ class admin_setting_configselect extends admin_setting {
* @return bool true if loaded, false if error
*/
public function load_choices() {
/*
if (is_array($this->choices)) {
if ($this->choiceloader) {
if (!is_array($this->choices)) {
$this->choices = call_user_func($this->choiceloader);
}
return true;
}
.... load choices here
*/
return true;
}
@ -3323,9 +3347,32 @@ class admin_setting_configselect extends admin_setting {
return ''; // ignore it
}
// Validate the new setting.
$error = $this->validate_setting($data);
if ($error) {
return $error;
}
return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Validate the setting. This uses the callback function if provided; subclasses could override
* to carry out validation directly in the class.
*
* @param string $data New value being set
* @return string Empty string if valid, or error message text
* @since Moodle 4.0
*/
protected function validate_setting(string $data): string {
// If validation function is specified, call it now.
if ($this->validatefunction) {
return call_user_func($this->validatefunction, $data);
} else {
return '';
}
}
/**
* Returns XHTML select field
*
@ -3423,7 +3470,6 @@ class admin_setting_configselect extends admin_setting {
}
}
/**
* Select multiple items from list
*

View File

@ -32,6 +32,10 @@ information provided here is intended especially for developers.
* The `core_output_load_fontawesome_icon_map` web service has been deprecated and replaced by
`core_output_load_fontawesome_icon_system_map` which takes the name of the theme to generate the icon system map for.
* The class coursecat_sortable_records has been removed.
* Admin setting admin_setting_configselect now supports lazy-loading the options list by supplying
a callback function instead of an array of options.
* Admin setting admin_setting_configselect now supports validating the selection by supplying a
callback function.
=== 3.9 ===
* Following function has been deprecated, please use \core\task\manager::run_from_cli().

View File

@ -84,9 +84,13 @@ abstract class engine {
*
* Search engine availability should be checked separately.
*
* The alternate configuration option is only used to construct a special second copy of the
* search engine object, as described in {@see has_alternate_configuration}.
*
* @param bool $alternateconfiguration If true, use alternate configuration settings
* @return void
*/
public function __construct() {
public function __construct(bool $alternateconfiguration = false) {
$classname = get_class($this);
if (strpos($classname, '\\') === false) {
@ -102,6 +106,19 @@ abstract class engine {
} else {
$this->config = new stdClass();
}
// For alternate configuration, automatically replace normal configuration values with
// those beginning with 'alternate'.
if ($alternateconfiguration) {
foreach ((array)$this->config as $key => $value) {
if (preg_match('~^alternate(.*)$~', $key, $matches)) {
$this->config->{$matches[1]} = $value;
}
}
}
// Flag just in case engine needs to know it is using the alternate configuration.
$this->config->alternateconfiguration = $alternateconfiguration;
}
/**
@ -740,4 +757,24 @@ abstract class engine {
public function get_batch_max_content(): int {
return 1024 * 1024;
}
/**
* Checks if the search engine has an alternate configuration.
*
* This is used where the same search engine class supports two different configurations,
* which are both shown on the settings screen. The alternate configuration is selected by
* passing 'true' parameter to the constructor.
*
* The feature is used when a different connection is in use for indexing vs. querying
* the search engine.
*
* This function should only return true if the engine supports an alternate configuration
* and the user has filled in the settings. (We do not need to test they are valid, that will
* happen as normal.)
*
* @return bool True if an alternate configuration is defined
*/
public function has_alternate_configuration(): bool {
return false;
}
}

View File

@ -190,13 +190,18 @@ class manager {
* parameter provides a way to skip those checks on pages which are used frequently. It has
* no effect if an instance has already been constructed in this request.
*
* The $query parameter indicates that the page is used for queries rather than indexing. If
* configured, this will cause the query-only search engine to be used instead of the 'normal'
* one.
*
* @see \core_search\engine::is_installed
* @see \core_search\engine::is_server_ready
* @param bool $fast Set to true when calling on a page that requires high performance
* @param bool $query Set true on a page that is used for querying
* @throws \core_search\engine_exception
* @return \core_search\manager
*/
public static function instance($fast = false) {
public static function instance(bool $fast = false, bool $query = false) {
global $CFG;
// One per request, this should be purged during testing.
@ -208,7 +213,7 @@ class manager {
throw new \core_search\engine_exception('enginenotselected', 'search');
}
if (!$engine = static::search_engine_instance()) {
if (!$engine = static::search_engine_instance($query)) {
throw new \core_search\engine_exception('enginenotfound', 'search', '', $CFG->searchengine);
}
@ -287,17 +292,46 @@ class manager {
/**
* Returns an instance of the search engine.
*
* @param bool $query If true, gets the query-only search engine (where configured)
* @return \core_search\engine
*/
public static function search_engine_instance() {
public static function search_engine_instance(bool $query = false) {
global $CFG;
$classname = '\\search_' . $CFG->searchengine . '\\engine';
if (!class_exists($classname)) {
return false;
if ($query && $CFG->searchenginequeryonly) {
return self::search_engine_instance_from_setting($CFG->searchenginequeryonly);
} else {
return self::search_engine_instance_from_setting($CFG->searchengine);
}
}
/**
* Loads a search engine based on the name given in settings, which can optionally
* include '-alternate' to indicate that an alternate version should be used.
*
* @param string $setting
* @return engine|null
*/
protected static function search_engine_instance_from_setting(string $setting): ?engine {
if (preg_match('~^(.*)-alternate$~', $setting, $matches)) {
$enginename = $matches[1];
$alternate = true;
} else {
$enginename = $setting;
$alternate = false;
}
return new $classname();
$classname = '\\search_' . $enginename . '\\engine';
if (!class_exists($classname)) {
return null;
}
if ($alternate) {
return new $classname(true);
} else {
// Use the constructor with no parameters for compatibility.
return new $classname();
}
}
/**

View File

@ -116,10 +116,11 @@ class engine extends \core_search\engine {
/**
* Initialises the search engine configuration.
*
* @param bool $alternateconfiguration If true, use alternate configuration settings
* @return void
*/
public function __construct() {
parent::__construct();
public function __construct(bool $alternateconfiguration = false) {
parent::__construct($alternateconfiguration);
$curlversion = curl_version();
if (isset($curlversion['version']) && stripos($curlversion['version'], '7.35.') === 0) {
@ -1256,7 +1257,7 @@ class engine extends \core_search\engine {
// Check that the schema is already set up.
try {
$schema = new \search_solr\schema();
$schema = new schema($this);
$schema->validate_setup();
} catch (\moodle_exception $e) {
return $e->getMessage();
@ -1466,7 +1467,7 @@ class engine extends \core_search\engine {
protected function update_schema($oldversion, $newversion) {
// Construct schema.
$schema = new schema();
$schema = new schema($this);
$cansetup = $schema->can_setup_server();
if ($cansetup !== true) {
return $cansetup;
@ -1564,4 +1565,15 @@ class engine extends \core_search\engine {
throw new \core_search\engine_exception('error_solr', 'search_solr', '', $e->getMessage());
}
}
/**
* Checks if an alternate configuration has been defined.
*
* @return bool True if alternate configuration is available
*/
public function has_alternate_configuration(): bool {
return !empty($this->config->alternateserver_hostname) &&
!empty($this->config->alternateindexname) &&
!empty($this->config->alternateserver_port);
}
}

View File

@ -60,10 +60,11 @@ class schema {
/**
* Constructor.
*
* @param engine $engine Optional engine parameter, if not specified then one will be created
* @throws \moodle_exception
* @return void
*/
public function __construct() {
public function __construct(engine $engine = null) {
if (!$this->config = get_config('search_solr')) {
throw new \moodle_exception('missingconfig', 'search_solr');
}
@ -72,7 +73,7 @@ class schema {
throw new \moodle_exception('missingconfig', 'search_solr');
}
$this->engine = new engine();
$this->engine = $engine ?? new engine();
$this->curl = $this->engine->get_curl_object();
// HTTP headers.

View File

@ -57,6 +57,42 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configtext('search_solr/maxindexfilekb',
new lang_string('maxindexfilekb', 'search_solr'),
new lang_string('maxindexfilekb_help', 'search_solr'), '2097152', PARAM_INT));
// Alternate connection.
$settings->add(new admin_setting_heading('search_solr_alternatesettings',
new lang_string('searchalternatesettings', 'admin'),
new lang_string('searchalternatesettings_desc', 'admin')));
$settings->add(new admin_setting_configtext('search_solr/alternateserver_hostname',
new lang_string('solrserverhostname', 'search_solr'),
new lang_string('solrserverhostname_desc', 'search_solr'), '127.0.0.1', PARAM_HOST));
$settings->add(new admin_setting_configtext('search_solr/alternateindexname',
new lang_string('solrindexname', 'search_solr'), '', '', PARAM_ALPHANUMEXT));
$settings->add(new admin_setting_configcheckbox('search_solr/alternatesecure',
new lang_string('solrsecuremode', 'search_solr'), '', 0, 1, 0));
$secure = get_config('search_solr', 'alternatesecure');
$defaultport = !empty($secure) ? 8443 : 8983;
$settings->add(new admin_setting_configtext('search_solr/alternateserver_port',
new lang_string('solrhttpconnectionport', 'search_solr'), '', $defaultport, PARAM_INT));
$settings->add(new admin_setting_configtext('search_solr/alternateserver_username',
new lang_string('solrauthuser', 'search_solr'), '', '', PARAM_RAW));
$settings->add(new admin_setting_configpasswordunmask('search_solr/alternateserver_password',
new lang_string('solrauthpassword', 'search_solr'), '', ''));
$settings->add(new admin_setting_configtext('search_solr/alternatessl_cert',
new lang_string('solrsslcert', 'search_solr'),
new lang_string('solrsslcert_desc', 'search_solr'), '', PARAM_RAW));
$settings->add(new admin_setting_configtext('search_solr/alternatessl_key',
new lang_string('solrsslkey', 'search_solr'),
new lang_string('solrsslkey_desc', 'search_solr'), '', PARAM_RAW));
$settings->add(new admin_setting_configpasswordunmask('search_solr/alternatessl_keypassword',
new lang_string('solrsslkeypassword', 'search_solr'),
new lang_string('solrsslkeypassword_desc', 'search_solr'), ''));
$settings->add(new admin_setting_configtext('search_solr/alternatessl_cainfo',
new lang_string('solrsslcainfo', 'search_solr'),
new lang_string('solrsslcainfo_desc', 'search_solr'), '', PARAM_RAW));
$settings->add(new admin_setting_configtext('search_solr/alternatessl_capath',
new lang_string('solrsslcapath', 'search_solr'),
new lang_string('solrsslcapath_desc', 'search_solr'), '', PARAM_RAW));
}
}
}

View File

@ -132,7 +132,7 @@ class search_solr_engine_testcase extends advanced_testcase {
$this->search->delete_index();
// Add moodle fields if they don't exist.
$schema = new \search_solr\schema();
$schema = new \search_solr\schema($this->engine);
$schema->setup(false);
}
@ -159,6 +159,45 @@ class search_solr_engine_testcase extends advanced_testcase {
$this->assertTrue($this->engine->is_server_ready());
}
/**
* Tests that the alternate settings are used when configured.
*/
public function test_alternate_settings() {
// Index a couple of things.
$this->generator->create_record();
$this->generator->create_record();
$this->search->index();
// By default settings, alternates are not set.
$this->assertFalse($this->engine->has_alternate_configuration());
// Set up all the config the same as normal.
foreach (['server_hostname', 'indexname', 'secure', 'server_port',
'server_username', 'server_password'] as $setting) {
set_config('alternate' . $setting, get_config('search_solr', $setting), 'search_solr');
}
// Also mess up the normal config.
set_config('indexname', 'not_the_right_index_name', 'search_solr');
// Construct a new engine using normal settings.
$engine = new search_solr\engine();
// Now alternates are available.
$this->assertTrue($engine->has_alternate_configuration());
// But it won't actually work because of the bogus index name.
$this->assertFalse($engine->is_server_ready() === true);
$this->assertDebuggingCalled();
// But if we construct one using alternate settings, it will work as normal.
$engine = new search_solr\engine(true);
$this->assertTrue($engine->is_server_ready());
// Including finding the search results.
$this->assertCount(2, $engine->execute_query(
(object)['q' => 'message'], (object)['everything' => true]));
}
/**
* @dataProvider file_indexing_provider
*/

View File

@ -64,7 +64,7 @@ if (\core_search\manager::is_global_search_enabled() === false) {
exit;
}
$search = \core_search\manager::instance(true);
$search = \core_search\manager::instance(true, true);
// Set up custom data for form.
$customdata = ['searchengine' => $search->get_engine()->get_plugin_name()];
@ -176,6 +176,11 @@ if ($data) {
$results = $search->paged_search($data, $page);
}
// Show search information if configured by system administrator.
if ($CFG->searchbannerenable && $CFG->searchbanner) {
echo $OUTPUT->notification(format_text($CFG->searchbanner, FORMAT_HTML), 'notifywarning');
}
if ($errorstr = $search->get_engine()->get_query_error()) {
echo $OUTPUT->notification(get_string('queryerror', 'search', $errorstr), 'notifyproblem');
} else if (empty($results->totalcount) && !empty($data)) {

View File

@ -0,0 +1,36 @@
@core @core_search
Feature: Show system information in the search interface
In order to let users know if there are current problems with search
As an admin
I need to be able to show information on search pages
Background:
Given the following config values are set as admin:
| enableglobalsearch | 1 |
| searchengine | simpledb |
And I log in as "admin"
@javascript
Scenario: Information displays when enabled
When the following config values are set as admin:
| searchbannerenable | 1 |
| searchbanner | The search currently only finds frog-related content; we hope to fix it soon. |
And I search for "toads" using the header global search box
Then I should see "The search currently only finds frog-related content" in the ".notifywarning" "css_element"
@javascript
Scenario: Information does not display when not enabled
When the following config values are set as admin:
| searchbannerenable | 0 |
| searchbanner | The search currently only finds frog-related content; we hope to fix it soon. |
And I search for "toads" using the header global search box
Then I should not see "The search currently only finds frog-related content"
And ".notifywarning" "css_element" should not exist
@javascript
Scenario: Information does not display when left blank
When the following config values are set as admin:
| searchbannerenable | 1 |
| searchbanner | |
And I search for "toads" using the header global search box
Then ".notifywarning" "css_element" should not exist

View File

@ -46,9 +46,10 @@ class testable_core_search extends \core_search\manager {
* Auto enables global search.
*
* @param \core_search\engine|bool $searchengine
* @param bool $ignored Second param just to make this compatible with base class
* @return testable_core_search
*/
public static function instance($searchengine = false) {
public static function instance($searchengine = false, bool $ignored = false) {
// One per request, this should be purged during testing.
if (self::$instance !== null) {

View File

@ -8,6 +8,11 @@ information provided here is intended especially for developers.
should implement add_document_batch() function and return true to supports_add_document_batch().
There is also an additional parameter returned from add_documents() with the number of batches
sent, which is used for the log display. Existing engines should continue to work unmodified.
* Search engines can now implement the optional has_alternate_configuration() function to indicate
if they provide two different connection configurations (for use when moving between two search
engines of the same type). The constructor should also accept a boolean value (true = alternate);
passing this to the base class constructor will automatically switch in the alternate
configuration settings, provided they begin with 'alternate'.
=== 3.8 ===