See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 * Solr schema manipulation manager. 19 * 20 * @package search_solr 21 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace search_solr; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 require_once($CFG->dirroot . '/lib/filelib.php'); 30 31 /** 32 * Schema class to interact with Solr schema. 33 * 34 * At the moment it only implements create which should be enough for a basic 35 * moodle configuration in Solr. 36 * 37 * @package search_solr 38 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class schema { 42 43 /** 44 * @var stdClass 45 */ 46 protected $config = null; 47 48 /** 49 * cUrl instance. 50 * @var \curl 51 */ 52 protected $curl = null; 53 54 /** 55 * An engine instance. 56 * @var engine 57 */ 58 protected $engine = null; 59 60 /** 61 * Constructor. 62 * 63 * @throws \moodle_exception 64 * @return void 65 */ 66 public function __construct() { 67 if (!$this->config = get_config('search_solr')) { 68 throw new \moodle_exception('missingconfig', 'search_solr'); 69 } 70 71 if (empty($this->config->server_hostname) || empty($this->config->indexname)) { 72 throw new \moodle_exception('missingconfig', 'search_solr'); 73 } 74 75 $this->engine = new engine(); 76 $this->curl = $this->engine->get_curl_object(); 77 78 // HTTP headers. 79 $this->curl->setHeader('Content-type: application/json'); 80 } 81 82 /** 83 * Can setup be executed against the configured server. 84 * 85 * @return true|string True or error message. 86 */ 87 public function can_setup_server() { 88 89 $status = $this->engine->is_server_configured(); 90 if ($status !== true) { 91 return $status; 92 } 93 94 // At this stage we know that the server is properly configured with a valid host:port and indexname. 95 // We're not too concerned about repeating the SolrClient::system() call (already called in 96 // is_server_configured) because this is just a setup script. 97 if ($this->engine->get_solr_major_version() < 5) { 98 // Schema setup script only available for 5.0 onwards. 99 return get_string('schemasetupfromsolr5', 'search_solr'); 100 } 101 102 return true; 103 } 104 105 /** 106 * Setup solr stuff required by moodle. 107 * 108 * @param bool $checkexisting Whether to check if the fields already exist or not 109 * @return bool 110 */ 111 public function setup($checkexisting = true) { 112 $fields = \search_solr\document::get_default_fields_definition(); 113 114 // Field id is already there. 115 unset($fields['id']); 116 117 $this->check_index(); 118 119 $return = $this->add_fields($fields, $checkexisting); 120 121 // Tell the engine we are now using the latest schema version. 122 $this->engine->record_applied_schema_version(document::SCHEMA_VERSION); 123 124 return $return; 125 } 126 127 /** 128 * Checks the schema is properly set up. 129 * 130 * @throws \moodle_exception 131 * @return void 132 */ 133 public function validate_setup() { 134 $fields = \search_solr\document::get_default_fields_definition(); 135 136 // Field id is already there. 137 unset($fields['id']); 138 139 $this->check_index(); 140 $this->validate_fields($fields, true); 141 } 142 143 /** 144 * Checks if the index is ready, triggers an exception otherwise. 145 * 146 * @throws \moodle_exception 147 * @return void 148 */ 149 protected function check_index() { 150 151 // Check that the server is available and the index exists. 152 $url = $this->engine->get_connection_url('/select?wt=json'); 153 $result = $this->curl->get($url); 154 if ($this->curl->error) { 155 throw new \moodle_exception('connectionerror', 'search_solr'); 156 } 157 if ($this->curl->info['http_code'] === 404) { 158 throw new \moodle_exception('connectionerror', 'search_solr'); 159 } 160 } 161 162 /** 163 * Adds the provided fields to Solr schema. 164 * 165 * Intentionally separated from create(), it can be called to add extra fields. 166 * fields separately. 167 * 168 * @throws \coding_exception 169 * @throws \moodle_exception 170 * @param array $fields \core_search\document::$requiredfields format 171 * @param bool $checkexisting Whether to check if the fields already exist or not 172 * @return bool 173 */ 174 protected function add_fields($fields, $checkexisting = true) { 175 176 if ($checkexisting) { 177 // Check that non of them exists. 178 $this->validate_fields($fields, false); 179 } 180 181 $url = $this->engine->get_connection_url('/schema'); 182 183 // Add all fields. 184 foreach ($fields as $fieldname => $data) { 185 186 if (!isset($data['type']) || !isset($data['stored']) || !isset($data['indexed'])) { 187 throw new \coding_exception($fieldname . ' does not define all required field params: type, stored and indexed.'); 188 } 189 $type = $this->doc_field_to_solr_field($data['type']); 190 191 // Changing default multiValued value to false as we want to match values easily. 192 $params = array( 193 'add-field' => array( 194 'name' => $fieldname, 195 'type' => $type, 196 'stored' => $data['stored'], 197 'multiValued' => false, 198 'indexed' => $data['indexed'] 199 ) 200 ); 201 $results = $this->curl->post($url, json_encode($params)); 202 203 // We only validate if we are interested on it. 204 if ($checkexisting) { 205 if ($this->curl->error) { 206 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $this->curl->error); 207 } 208 $this->validate_add_field_result($results); 209 } 210 } 211 212 return true; 213 } 214 215 /** 216 * Checks if the schema existing fields are properly set, triggers an exception otherwise. 217 * 218 * @throws \moodle_exception 219 * @param array $fields 220 * @param bool $requireexisting Require the fields to exist, otherwise exception. 221 * @return void 222 */ 223 protected function validate_fields(&$fields, $requireexisting = false) { 224 global $CFG; 225 226 foreach ($fields as $fieldname => $data) { 227 $url = $this->engine->get_connection_url('/schema/fields/' . $fieldname); 228 $results = $this->curl->get($url); 229 230 if ($this->curl->error) { 231 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $this->curl->error); 232 } 233 234 if (!$results) { 235 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', get_string('nodatafromserver', 'search_solr')); 236 } 237 $results = json_decode($results); 238 239 if ($requireexisting && !empty($results->error) && $results->error->code === 404) { 240 $a = new \stdClass(); 241 $a->fieldname = $fieldname; 242 $a->setupurl = $CFG->wwwroot . '/search/engine/solr/setup_schema.php'; 243 throw new \moodle_exception('errorvalidatingschema', 'search_solr', '', $a); 244 } 245 246 // The field should not exist so we only accept 404 errors. 247 if (empty($results->error) || (!empty($results->error) && $results->error->code !== 404)) { 248 if (!empty($results->error)) { 249 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $results->error->msg); 250 } else { 251 // All these field attributes are set when fields are added through this script and should 252 // be returned and match the defined field's values. 253 254 $expectedsolrfield = $this->doc_field_to_solr_field($data['type']); 255 if (empty($results->field) || !isset($results->field->type) || 256 !isset($results->field->multiValued) || !isset($results->field->indexed) || 257 !isset($results->field->stored)) { 258 259 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', 260 get_string('schemafieldautocreated', 'search_solr', $fieldname)); 261 262 } else if ($results->field->type !== $expectedsolrfield || 263 $results->field->multiValued !== false || 264 $results->field->indexed !== $data['indexed'] || 265 $results->field->stored !== $data['stored']) { 266 267 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', 268 get_string('schemafieldautocreated', 'search_solr', $fieldname)); 269 } else { 270 // The field already exists and it is properly defined, no need to create it. 271 unset($fields[$fieldname]); 272 } 273 } 274 } 275 } 276 } 277 278 /** 279 * Checks that the field results do not contain errors. 280 * 281 * @throws \moodle_exception 282 * @param string $results curl response body 283 * @return void 284 */ 285 protected function validate_add_field_result($result) { 286 287 if (!$result) { 288 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', get_string('nodatafromserver', 'search_solr')); 289 } 290 291 $results = json_decode($result); 292 if (!$results) { 293 if (is_scalar($result)) { 294 $errormsg = $result; 295 } else { 296 $errormsg = json_encode($result); 297 } 298 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $errormsg); 299 } 300 301 // It comes as error when fetching fields data. 302 if (!empty($results->error)) { 303 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $results->error); 304 } 305 306 // It comes as errors when adding fields. 307 if (!empty($results->errors)) { 308 309 // We treat this error separately. 310 $errorstr = ''; 311 foreach ($results->errors as $error) { 312 $errorstr .= implode(', ', $error->errorMessages); 313 } 314 throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $errorstr); 315 } 316 317 } 318 319 /** 320 * Returns the solr field type from the document field type string. 321 * 322 * @param string $datatype 323 * @return string 324 */ 325 private function doc_field_to_solr_field($datatype) { 326 $type = $datatype; 327 328 $solrversion = $this->engine->get_solr_major_version(); 329 330 switch($datatype) { 331 case 'text': 332 $type = 'text_general'; 333 break; 334 case 'int': 335 if ($solrversion >= 7) { 336 $type = 'pint'; 337 } 338 break; 339 case 'tdate': 340 if ($solrversion >= 7) { 341 $type = 'pdate'; 342 } 343 break; 344 } 345 346 return $type; 347 } 348 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body