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