Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}