Commit | Line | Data |
---|---|---|
9a0df45a | 1 | <?php |
9a0df45a | 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 | ||
4615817d | 17 | |
9a0df45a | 18 | /** |
19 | * Support for external API | |
20 | * | |
4615817d JM |
21 | * @package core_webservice |
22 | * @copyright 2009 Petr Skodak | |
9a0df45a | 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
24 | */ | |
25 | ||
78bfb562 | 26 | defined('MOODLE_INTERNAL') || die(); |
1942103f | 27 | |
9a0df45a | 28 | /** |
4615817d JM |
29 | * Exception indicating user is not allowed to use external function in the current context. |
30 | * | |
31 | * @package core_webservice | |
32 | * @copyright 2009 Petr Skodak | |
33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
34 | * @since Moodle 2.0 | |
9a0df45a | 35 | */ |
36 | class restricted_context_exception extends moodle_exception { | |
37 | /** | |
38 | * Constructor | |
4615817d JM |
39 | * |
40 | * @since Moodle 2.0 | |
9a0df45a | 41 | */ |
42 | function __construct() { | |
43 | parent::__construct('restrictedcontextexception', 'error'); | |
44 | } | |
45 | } | |
46 | ||
47 | /** | |
48 | * Base class for external api methods. | |
4615817d JM |
49 | * |
50 | * @package core_webservice | |
51 | * @copyright 2009 Petr Skodak | |
52 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
53 | * @since Moodle 2.0 | |
9a0df45a | 54 | */ |
55 | class external_api { | |
4615817d JM |
56 | |
57 | /** @var stdClass context where the function calls will be restricted */ | |
9a0df45a | 58 | private static $contextrestriction; |
59 | ||
56fa860e DW |
60 | /** |
61 | * Returns detailed function information | |
62 | * | |
63 | * @param string|object $function name of external function or record from external_function | |
64 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; | |
65 | * MUST_EXIST means throw exception if no record or multiple records found | |
66 | * @return stdClass description or false if not found or exception thrown | |
67 | * @since Moodle 2.0 | |
68 | */ | |
69 | public static function external_function_info($function, $strictness=MUST_EXIST) { | |
70 | global $DB, $CFG; | |
71 | ||
72 | if (!is_object($function)) { | |
73 | if (!$function = $DB->get_record('external_functions', array('name' => $function), '*', $strictness)) { | |
74 | return false; | |
75 | } | |
76 | } | |
77 | ||
78 | // First try class autoloading. | |
79 | if (!class_exists($function->classname)) { | |
80 | // Fallback to explicit include of externallib.php. | |
81 | if (empty($function->classpath)) { | |
82 | $function->classpath = core_component::get_component_directory($function->component).'/externallib.php'; | |
83 | } else { | |
84 | $function->classpath = $CFG->dirroot.'/'.$function->classpath; | |
85 | } | |
86 | if (!file_exists($function->classpath)) { | |
87 | throw new coding_exception('Cannot find file with external function implementation'); | |
88 | } | |
89 | require_once($function->classpath); | |
90 | if (!class_exists($function->classname)) { | |
91 | throw new coding_exception('Cannot find external class'); | |
92 | } | |
93 | } | |
94 | ||
95 | $function->ajax_method = $function->methodname.'_is_allowed_from_ajax'; | |
96 | $function->parameters_method = $function->methodname.'_parameters'; | |
97 | $function->returns_method = $function->methodname.'_returns'; | |
98 | $function->deprecated_method = $function->methodname.'_is_deprecated'; | |
99 | ||
100 | // Make sure the implementaion class is ok. | |
101 | if (!method_exists($function->classname, $function->methodname)) { | |
102 | throw new coding_exception('Missing implementation method of '.$function->classname.'::'.$function->methodname); | |
103 | } | |
104 | if (!method_exists($function->classname, $function->parameters_method)) { | |
105 | throw new coding_exception('Missing parameters description'); | |
106 | } | |
107 | if (!method_exists($function->classname, $function->returns_method)) { | |
108 | throw new coding_exception('Missing returned values description'); | |
109 | } | |
110 | if (method_exists($function->classname, $function->deprecated_method)) { | |
111 | if (call_user_func(array($function->classname, $function->deprecated_method)) === true) { | |
112 | $function->deprecated = true; | |
113 | } | |
114 | } | |
115 | $function->allowed_from_ajax = false; | |
116 | ||
117 | // Fetch the parameters description. | |
118 | $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method)); | |
119 | if (!($function->parameters_desc instanceof external_function_parameters)) { | |
120 | throw new coding_exception('Invalid parameters description'); | |
121 | } | |
122 | ||
123 | // Fetch the return values description. | |
124 | $function->returns_desc = call_user_func(array($function->classname, $function->returns_method)); | |
125 | // Null means void result or result is ignored. | |
126 | if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) { | |
127 | throw new coding_exception('Invalid return description'); | |
128 | } | |
129 | ||
130 | // Now get the function description. | |
131 | ||
132 | // TODO MDL-31115 use localised lang pack descriptions, it would be nice to have | |
133 | // easy to understand descriptions in admin UI, | |
134 | // on the other hand this is still a bit in a flux and we need to find some new naming | |
135 | // conventions for these descriptions in lang packs. | |
136 | $function->description = null; | |
137 | $servicesfile = core_component::get_component_directory($function->component).'/db/services.php'; | |
138 | if (file_exists($servicesfile)) { | |
139 | $functions = null; | |
140 | include($servicesfile); | |
141 | if (isset($functions[$function->name]['description'])) { | |
142 | $function->description = $functions[$function->name]['description']; | |
143 | } | |
144 | if (isset($functions[$function->name]['testclientpath'])) { | |
145 | $function->testclientpath = $functions[$function->name]['testclientpath']; | |
146 | } | |
147 | if (isset($functions[$function->name]['type'])) { | |
148 | $function->type = $functions[$function->name]['type']; | |
149 | } | |
150 | if (isset($functions[$function->name]['ajax'])) { | |
151 | $function->allowed_from_ajax = $functions[$function->name]['ajax']; | |
152 | } else if (method_exists($function->classname, $function->ajax_method)) { | |
153 | if (call_user_func(array($function->classname, $function->ajax_method)) === true) { | |
154 | debugging('External function ' . $function->ajax_method . '() function is deprecated.' . | |
155 | 'Set ajax=>true in db/service.php instead.', DEBUG_DEVELOPER); | |
156 | $function->allowed_from_ajax = true; | |
157 | } | |
158 | } | |
159 | if (isset($functions[$function->name]['loginrequired'])) { | |
160 | $function->loginrequired = $functions[$function->name]['loginrequired']; | |
161 | } else { | |
162 | $function->loginrequired = true; | |
163 | } | |
164 | } | |
165 | ||
166 | return $function; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Call an external function validating all params/returns correctly. | |
171 | * | |
172 | * Note that an external function may modify the state of the current page, so this wrapper | |
173 | * saves and restores tha PAGE and COURSE global variables before/after calling the external function. | |
174 | * | |
175 | * @param string $function A webservice function name. | |
176 | * @param array $args Params array (named params) | |
177 | * @param boolean $ajaxonly If true, an extra check will be peformed to see if ajax is required. | |
178 | * @return array containing keys for error (bool), exception and data. | |
179 | */ | |
180 | public static function call_external_function($function, $args, $ajaxonly=false) { | |
181 | global $PAGE, $COURSE, $CFG, $SITE; | |
182 | ||
183 | require_once($CFG->libdir . "/pagelib.php"); | |
184 | ||
185 | $externalfunctioninfo = self::external_function_info($function); | |
186 | ||
187 | $currentpage = $PAGE; | |
188 | $currentcourse = $COURSE; | |
189 | $response = array(); | |
190 | ||
191 | try { | |
e02e7f5a DW |
192 | // Taken straight from from setup.php. |
193 | if (!empty($CFG->moodlepageclass)) { | |
194 | if (!empty($CFG->moodlepageclassfile)) { | |
195 | require_once($CFG->moodlepageclassfile); | |
196 | } | |
197 | $classname = $CFG->moodlepageclass; | |
198 | } else { | |
199 | $classname = 'moodle_page'; | |
200 | } | |
201 | $PAGE = new $classname(); | |
56fa860e DW |
202 | $COURSE = clone($SITE); |
203 | ||
204 | if ($ajaxonly && !$externalfunctioninfo->allowed_from_ajax) { | |
205 | throw new moodle_exception('servicenotavailable', 'webservice'); | |
206 | } | |
207 | ||
b9b409cf | 208 | // Do not allow access to write or delete webservices as a public user. |
f19beb32 | 209 | if ($externalfunctioninfo->loginrequired && !WS_SERVER) { |
56fa860e | 210 | if (defined('NO_MOODLE_COOKIES') && NO_MOODLE_COOKIES && !PHPUNIT_TEST) { |
acf94de6 | 211 | throw new moodle_exception('servicerequireslogin', 'webservice'); |
56fa860e DW |
212 | } |
213 | if (!isloggedin()) { | |
acf94de6 | 214 | throw new moodle_exception('servicerequireslogin', 'webservice'); |
56fa860e DW |
215 | } else { |
216 | require_sesskey(); | |
217 | } | |
218 | } | |
56fa860e DW |
219 | // Validate params, this also sorts the params properly, we need the correct order in the next part. |
220 | $callable = array($externalfunctioninfo->classname, 'validate_parameters'); | |
221 | $params = call_user_func($callable, | |
222 | $externalfunctioninfo->parameters_desc, | |
223 | $args); | |
b5311ce4 | 224 | $params = array_values($params); |
56fa860e | 225 | |
b5311ce4 | 226 | // Allow any Moodle plugin a chance to override this call. This is a convenient spot to |
227 | // make arbitrary behaviour customisations. The overriding plugin could call the 'real' | |
228 | // function first and then modify the results, or it could do a completely separate | |
229 | // thing. | |
230 | $callbacks = get_plugins_with_function('override_webservice_execution'); | |
231 | $result = false; | |
232 | foreach ($callbacks as $plugintype => $plugins) { | |
233 | foreach ($plugins as $plugin => $callback) { | |
234 | $result = $callback($externalfunctioninfo, $params); | |
235 | if ($result !== false) { | |
236 | break; | |
237 | } | |
238 | } | |
239 | } | |
240 | ||
241 | // If the function was not overridden, call the real one. | |
242 | if ($result === false) { | |
243 | $callable = array($externalfunctioninfo->classname, $externalfunctioninfo->methodname); | |
244 | $result = call_user_func_array($callable, $params); | |
245 | } | |
56fa860e DW |
246 | |
247 | // Validate the return parameters. | |
248 | if ($externalfunctioninfo->returns_desc !== null) { | |
249 | $callable = array($externalfunctioninfo->classname, 'clean_returnvalue'); | |
250 | $result = call_user_func($callable, $externalfunctioninfo->returns_desc, $result); | |
251 | } | |
252 | ||
253 | $response['error'] = false; | |
254 | $response['data'] = $result; | |
255 | } catch (Exception $e) { | |
256 | $exception = get_exception_info($e); | |
257 | unset($exception->a); | |
1e17258b | 258 | $exception->backtrace = format_backtrace($exception->backtrace, true); |
56fa860e DW |
259 | if (!debugging('', DEBUG_DEVELOPER)) { |
260 | unset($exception->debuginfo); | |
261 | unset($exception->backtrace); | |
262 | } | |
263 | $response['error'] = true; | |
264 | $response['exception'] = $exception; | |
265 | // Do not process the remaining requests. | |
266 | } | |
267 | ||
268 | $PAGE = $currentpage; | |
269 | $COURSE = $currentcourse; | |
270 | ||
271 | return $response; | |
272 | } | |
273 | ||
1bea0c27 | 274 | /** |
23e7b7cc | 275 | * Set context restriction for all following subsequent function calls. |
4615817d JM |
276 | * |
277 | * @param stdClass $context the context restriction | |
278 | * @since Moodle 2.0 | |
1bea0c27 | 279 | */ |
2965d271 | 280 | public static function set_context_restriction($context) { |
9a0df45a | 281 | self::$contextrestriction = $context; |
282 | } | |
283 | ||
2965d271 | 284 | /** |
285 | * This method has to be called before every operation | |
286 | * that takes a longer time to finish! | |
287 | * | |
288 | * @param int $seconds max expected time the next operation needs | |
4615817d | 289 | * @since Moodle 2.0 |
2965d271 | 290 | */ |
291 | public static function set_timeout($seconds=360) { | |
292 | $seconds = ($seconds < 300) ? 300 : $seconds; | |
3ef7279f | 293 | core_php_time_limit::raise($seconds); |
2965d271 | 294 | } |
295 | ||
1bea0c27 | 296 | /** |
c9c5cc81 | 297 | * Validates submitted function parameters, if anything is incorrect |
1bea0c27 | 298 | * invalid_parameter_exception is thrown. |
1d7db36f | 299 | * This is a simple recursive method which is intended to be called from |
300 | * each implementation method of external API. | |
4615817d | 301 | * |
c9c5cc81 | 302 | * @param external_description $description description of parameters |
303 | * @param mixed $params the actual parameters | |
304 | * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found | |
4615817d | 305 | * @since Moodle 2.0 |
1bea0c27 | 306 | */ |
c9c5cc81 | 307 | public static function validate_parameters(external_description $description, $params) { |
04d212ce | 308 | if ($description instanceof external_value) { |
c9c5cc81 | 309 | if (is_array($params) or is_object($params)) { |
93602569 | 310 | throw new invalid_parameter_exception('Scalar type expected, array or object received.'); |
c9c5cc81 | 311 | } |
4f0c6ad1 PS |
312 | |
313 | if ($description->type == PARAM_BOOL) { | |
314 | // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) | |
315 | if (is_bool($params) or $params === 0 or $params === 1 or $params === '0' or $params === '1') { | |
316 | return (bool)$params; | |
317 | } | |
318 | } | |
93602569 JM |
319 | $debuginfo = 'Invalid external api parameter: the value is "' . $params . |
320 | '", the server was expecting "' . $description->type . '" type'; | |
321 | return validate_param($params, $description->type, $description->allownull, $debuginfo); | |
4f0c2d00 | 322 | |
c9c5cc81 | 323 | } else if ($description instanceof external_single_structure) { |
324 | if (!is_array($params)) { | |
93602569 JM |
325 | throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' |
326 | . print_r($params, true) . '\''); | |
c9c5cc81 | 327 | } |
328 | $result = array(); | |
329 | foreach ($description->keys as $key=>$subdesc) { | |
330 | if (!array_key_exists($key, $params)) { | |
382b9cea | 331 | if ($subdesc->required == VALUE_REQUIRED) { |
93602569 | 332 | throw new invalid_parameter_exception('Missing required key in single structure: '. $key); |
c9c5cc81 | 333 | } |
774b1b0f | 334 | if ($subdesc->required == VALUE_DEFAULT) { |
335 | try { | |
d96030ce | 336 | $result[$key] = static::validate_parameters($subdesc, $subdesc->default); |
774b1b0f | 337 | } catch (invalid_parameter_exception $e) { |
93602569 JM |
338 | //we are only interested by exceptions returned by validate_param() and validate_parameters() |
339 | //(in order to build the path to the faulty attribut) | |
340 | throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); | |
382b9cea | 341 | } |
774b1b0f | 342 | } |
c9c5cc81 | 343 | } else { |
559a5dbd | 344 | try { |
d96030ce | 345 | $result[$key] = static::validate_parameters($subdesc, $params[$key]); |
559a5dbd | 346 | } catch (invalid_parameter_exception $e) { |
93602569 JM |
347 | //we are only interested by exceptions returned by validate_param() and validate_parameters() |
348 | //(in order to build the path to the faulty attribut) | |
349 | throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); | |
559a5dbd | 350 | } |
c9c5cc81 | 351 | } |
352 | unset($params[$key]); | |
353 | } | |
354 | if (!empty($params)) { | |
92fe97f9 | 355 | throw new invalid_parameter_exception('Unexpected keys (' . implode(', ', array_keys($params)) . ') detected in parameter array.'); |
c9c5cc81 | 356 | } |
357 | return $result; | |
1bea0c27 | 358 | |
c9c5cc81 | 359 | } else if ($description instanceof external_multiple_structure) { |
360 | if (!is_array($params)) { | |
93602569 JM |
361 | throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' |
362 | . print_r($params, true) . '\''); | |
c9c5cc81 | 363 | } |
364 | $result = array(); | |
365 | foreach ($params as $param) { | |
d96030ce | 366 | $result[] = static::validate_parameters($description->content, $param); |
c9c5cc81 | 367 | } |
368 | return $result; | |
369 | ||
370 | } else { | |
93602569 | 371 | throw new invalid_parameter_exception('Invalid external api description'); |
c9c5cc81 | 372 | } |
1bea0c27 | 373 | } |
374 | ||
d07ff72d | 375 | /** |
376 | * Clean response | |
23e7b7cc PS |
377 | * If a response attribute is unknown from the description, we just ignore the attribute. |
378 | * If a response attribute is incorrect, invalid_response_exception is thrown. | |
d07ff72d | 379 | * Note: this function is similar to validate parameters, however it is distinct because |
380 | * parameters validation must be distinct from cleaning return values. | |
4615817d | 381 | * |
d07ff72d | 382 | * @param external_description $description description of the return values |
383 | * @param mixed $response the actual response | |
384 | * @return mixed response with added defaults for optional items, invalid_response_exception thrown if any problem found | |
4615817d JM |
385 | * @author 2010 Jerome Mouneyrac |
386 | * @since Moodle 2.0 | |
d07ff72d | 387 | */ |
388 | public static function clean_returnvalue(external_description $description, $response) { | |
389 | if ($description instanceof external_value) { | |
390 | if (is_array($response) or is_object($response)) { | |
93602569 | 391 | throw new invalid_response_exception('Scalar type expected, array or object received.'); |
d07ff72d | 392 | } |
393 | ||
394 | if ($description->type == PARAM_BOOL) { | |
395 | // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) | |
396 | if (is_bool($response) or $response === 0 or $response === 1 or $response === '0' or $response === '1') { | |
397 | return (bool)$response; | |
398 | } | |
399 | } | |
27190655 | 400 | $responsetype = gettype($response); |
93602569 | 401 | $debuginfo = 'Invalid external api response: the value is "' . $response . |
27190655 | 402 | '" of PHP type "' . $responsetype . '", the server was expecting "' . $description->type . '" type'; |
93602569 JM |
403 | try { |
404 | return validate_param($response, $description->type, $description->allownull, $debuginfo); | |
405 | } catch (invalid_parameter_exception $e) { | |
406 | //proper exception name, to be recursively catched to build the path to the faulty attribut | |
407 | throw new invalid_response_exception($e->debuginfo); | |
408 | } | |
d07ff72d | 409 | |
410 | } else if ($description instanceof external_single_structure) { | |
9a4c2f50 JM |
411 | if (!is_array($response) && !is_object($response)) { |
412 | throw new invalid_response_exception('Only arrays/objects accepted. The bad value is: \'' . | |
93602569 | 413 | print_r($response, true) . '\''); |
d07ff72d | 414 | } |
9a4c2f50 JM |
415 | |
416 | // Cast objects into arrays. | |
417 | if (is_object($response)) { | |
418 | $response = (array) $response; | |
419 | } | |
420 | ||
d07ff72d | 421 | $result = array(); |
422 | foreach ($description->keys as $key=>$subdesc) { | |
423 | if (!array_key_exists($key, $response)) { | |
424 | if ($subdesc->required == VALUE_REQUIRED) { | |
93602569 | 425 | throw new invalid_response_exception('Error in response - Missing following required key in a single structure: ' . $key); |
d07ff72d | 426 | } |
427 | if ($subdesc instanceof external_value) { | |
143c0abe | 428 | if ($subdesc->required == VALUE_DEFAULT) { |
429 | try { | |
d96030ce | 430 | $result[$key] = static::clean_returnvalue($subdesc, $subdesc->default); |
93602569 JM |
431 | } catch (invalid_response_exception $e) { |
432 | //build the path to the faulty attribut | |
433 | throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); | |
d07ff72d | 434 | } |
435 | } | |
143c0abe | 436 | } |
d07ff72d | 437 | } else { |
438 | try { | |
d96030ce | 439 | $result[$key] = static::clean_returnvalue($subdesc, $response[$key]); |
93602569 JM |
440 | } catch (invalid_response_exception $e) { |
441 | //build the path to the faulty attribut | |
442 | throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); | |
d07ff72d | 443 | } |
444 | } | |
445 | unset($response[$key]); | |
446 | } | |
447 | ||
448 | return $result; | |
449 | ||
450 | } else if ($description instanceof external_multiple_structure) { | |
451 | if (!is_array($response)) { | |
93602569 JM |
452 | throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' . |
453 | print_r($response, true) . '\''); | |
d07ff72d | 454 | } |
455 | $result = array(); | |
456 | foreach ($response as $param) { | |
d96030ce | 457 | $result[] = static::clean_returnvalue($description->content, $param); |
d07ff72d | 458 | } |
459 | return $result; | |
460 | ||
461 | } else { | |
93602569 | 462 | throw new invalid_response_exception('Invalid external api response description'); |
d07ff72d | 463 | } |
464 | } | |
465 | ||
9a0df45a | 466 | /** |
467 | * Makes sure user may execute functions in this context. | |
4615817d JM |
468 | * |
469 | * @param stdClass $context | |
470 | * @since Moodle 2.0 | |
9a0df45a | 471 | */ |
82f0f5e3 | 472 | public static function validate_context($context) { |
56fa860e | 473 | global $CFG, $PAGE; |
4f0c2d00 | 474 | |
ab9a01f2 | 475 | if (empty($context)) { |
476 | throw new invalid_parameter_exception('Context does not exist'); | |
477 | } | |
9a0df45a | 478 | if (empty(self::$contextrestriction)) { |
b0c6dc1c | 479 | self::$contextrestriction = context_system::instance(); |
9a0df45a | 480 | } |
481 | $rcontext = self::$contextrestriction; | |
482 | ||
483 | if ($rcontext->contextlevel == $context->contextlevel) { | |
aa7fbebd | 484 | if ($rcontext->id != $context->id) { |
9a0df45a | 485 | throw new restricted_context_exception(); |
486 | } | |
487 | } else if ($rcontext->contextlevel > $context->contextlevel) { | |
488 | throw new restricted_context_exception(); | |
489 | } else { | |
8e8891b7 | 490 | $parents = $context->get_parent_context_ids(); |
9a0df45a | 491 | if (!in_array($rcontext->id, $parents)) { |
492 | throw new restricted_context_exception(); | |
493 | } | |
494 | } | |
495 | ||
56fa860e DW |
496 | $PAGE->reset_theme_and_output(); |
497 | list($unused, $course, $cm) = get_context_info_array($context->id); | |
498 | require_login($course, false, $cm, false, true); | |
499 | $PAGE->set_context($context); | |
9a0df45a | 500 | } |
aac70ffc AA |
501 | |
502 | /** | |
5b23d9ad AA |
503 | * Get context from passed parameters. |
504 | * The passed array must either contain a contextid or a combination of context level and instance id to fetch the context. | |
505 | * For example, the context level can be "course" and instanceid can be courseid. | |
506 | * | |
507 | * See context_helper::get_all_levels() for a list of valid context levels. | |
aac70ffc AA |
508 | * |
509 | * @param array $param | |
510 | * @since Moodle 2.6 | |
511 | * @throws invalid_parameter_exception | |
512 | * @return context | |
513 | */ | |
5b23d9ad | 514 | protected static function get_context_from_params($param) { |
aac70ffc | 515 | $levels = context_helper::get_all_levels(); |
98dece22 | 516 | if (!empty($param['contextid'])) { |
aac70ffc | 517 | return context::instance_by_id($param['contextid'], IGNORE_MISSING); |
eac1383e | 518 | } else if (!empty($param['contextlevel']) && isset($param['instanceid'])) { |
aac70ffc AA |
519 | $contextlevel = "context_".$param['contextlevel']; |
520 | if (!array_search($contextlevel, $levels)) { | |
521 | throw new invalid_parameter_exception('Invalid context level = '.$param['contextlevel']); | |
522 | } | |
523 | return $contextlevel::instance($param['instanceid'], IGNORE_MISSING); | |
524 | } else { | |
525 | // No valid context info was found. | |
526 | throw new invalid_parameter_exception('Missing parameters, please provide either context level with instance id or contextid'); | |
527 | } | |
528 | } | |
a60e8ba5 DW |
529 | |
530 | /** | |
531 | * Returns a prepared structure to use a context parameters. | |
532 | * @return external_single_structure | |
533 | */ | |
534 | protected static function get_context_parameters() { | |
535 | $id = new external_value( | |
536 | PARAM_INT, | |
537 | 'Context ID. Either use this value, or level and instanceid.', | |
538 | VALUE_DEFAULT, | |
539 | 0 | |
540 | ); | |
541 | $level = new external_value( | |
542 | PARAM_ALPHA, | |
543 | 'Context level. To be used with instanceid.', | |
544 | VALUE_DEFAULT, | |
545 | '' | |
546 | ); | |
547 | $instanceid = new external_value( | |
548 | PARAM_INT, | |
549 | 'Context instance ID. To be used with level', | |
550 | VALUE_DEFAULT, | |
551 | 0 | |
552 | ); | |
553 | return new external_single_structure(array( | |
554 | 'contextid' => $id, | |
555 | 'contextlevel' => $level, | |
556 | 'instanceid' => $instanceid, | |
557 | )); | |
558 | } | |
559 | ||
9a0df45a | 560 | } |
561 | ||
b038c32c | 562 | /** |
563 | * Common ancestor of all parameter description classes | |
4615817d JM |
564 | * |
565 | * @package core_webservice | |
566 | * @copyright 2009 Petr Skodak | |
567 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
568 | * @since Moodle 2.0 | |
b038c32c | 569 | */ |
570 | abstract class external_description { | |
4615817d | 571 | /** @var string Description of element */ |
b038c32c | 572 | public $desc; |
4615817d JM |
573 | |
574 | /** @var bool Element value required, null not allowed */ | |
b038c32c | 575 | public $required; |
4615817d JM |
576 | |
577 | /** @var mixed Default value */ | |
774b1b0f | 578 | public $default; |
b038c32c | 579 | |
580 | /** | |
581 | * Contructor | |
4615817d | 582 | * |
b038c32c | 583 | * @param string $desc |
584 | * @param bool $required | |
774b1b0f | 585 | * @param mixed $default |
4615817d | 586 | * @since Moodle 2.0 |
b038c32c | 587 | */ |
774b1b0f | 588 | public function __construct($desc, $required, $default) { |
b038c32c | 589 | $this->desc = $desc; |
590 | $this->required = $required; | |
774b1b0f | 591 | $this->default = $default; |
b038c32c | 592 | } |
593 | } | |
594 | ||
595 | /** | |
4615817d JM |
596 | * Scalar value description class |
597 | * | |
598 | * @package core_webservice | |
599 | * @copyright 2009 Petr Skodak | |
600 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
601 | * @since Moodle 2.0 | |
b038c32c | 602 | */ |
04d212ce | 603 | class external_value extends external_description { |
4615817d JM |
604 | |
605 | /** @var mixed Value type PARAM_XX */ | |
b038c32c | 606 | public $type; |
4615817d JM |
607 | |
608 | /** @var bool Allow null values */ | |
b038c32c | 609 | public $allownull; |
610 | ||
611 | /** | |
612 | * Constructor | |
4615817d | 613 | * |
b038c32c | 614 | * @param mixed $type |
615 | * @param string $desc | |
616 | * @param bool $required | |
617 | * @param mixed $default | |
618 | * @param bool $allownull | |
4615817d | 619 | * @since Moodle 2.0 |
b038c32c | 620 | */ |
774b1b0f | 621 | public function __construct($type, $desc='', $required=VALUE_REQUIRED, |
622 | $default=null, $allownull=NULL_ALLOWED) { | |
623 | parent::__construct($desc, $required, $default); | |
78bfb562 | 624 | $this->type = $type; |
b038c32c | 625 | $this->allownull = $allownull; |
626 | } | |
627 | } | |
628 | ||
629 | /** | |
630 | * Associative array description class | |
4615817d JM |
631 | * |
632 | * @package core_webservice | |
633 | * @copyright 2009 Petr Skodak | |
634 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
635 | * @since Moodle 2.0 | |
b038c32c | 636 | */ |
637 | class external_single_structure extends external_description { | |
4615817d JM |
638 | |
639 | /** @var array Description of array keys key=>external_description */ | |
b038c32c | 640 | public $keys; |
641 | ||
642 | /** | |
643 | * Constructor | |
4615817d | 644 | * |
b038c32c | 645 | * @param array $keys |
646 | * @param string $desc | |
647 | * @param bool $required | |
774b1b0f | 648 | * @param array $default |
4615817d | 649 | * @since Moodle 2.0 |
b038c32c | 650 | */ |
774b1b0f | 651 | public function __construct(array $keys, $desc='', |
652 | $required=VALUE_REQUIRED, $default=null) { | |
653 | parent::__construct($desc, $required, $default); | |
b038c32c | 654 | $this->keys = $keys; |
655 | } | |
656 | } | |
657 | ||
658 | /** | |
659 | * Bulk array description class. | |
4615817d JM |
660 | * |
661 | * @package core_webservice | |
662 | * @copyright 2009 Petr Skodak | |
663 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
664 | * @since Moodle 2.0 | |
b038c32c | 665 | */ |
666 | class external_multiple_structure extends external_description { | |
4615817d JM |
667 | |
668 | /** @var external_description content */ | |
b038c32c | 669 | public $content; |
670 | ||
671 | /** | |
672 | * Constructor | |
4615817d | 673 | * |
b038c32c | 674 | * @param external_description $content |
675 | * @param string $desc | |
676 | * @param bool $required | |
774b1b0f | 677 | * @param array $default |
4615817d | 678 | * @since Moodle 2.0 |
b038c32c | 679 | */ |
774b1b0f | 680 | public function __construct(external_description $content, $desc='', |
681 | $required=VALUE_REQUIRED, $default=null) { | |
682 | parent::__construct($desc, $required, $default); | |
b038c32c | 683 | $this->content = $content; |
684 | } | |
685 | } | |
c29cca30 | 686 | |
687 | /** | |
688 | * Description of top level - PHP function parameters. | |
c29cca30 | 689 | * |
4615817d JM |
690 | * @package core_webservice |
691 | * @copyright 2009 Petr Skodak | |
692 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
693 | * @since Moodle 2.0 | |
c29cca30 | 694 | */ |
695 | class external_function_parameters extends external_single_structure { | |
1dc4dc98 DW |
696 | |
697 | /** | |
698 | * Constructor - does extra checking to prevent top level optional parameters. | |
699 | * | |
700 | * @param array $keys | |
701 | * @param string $desc | |
702 | * @param bool $required | |
703 | * @param array $default | |
704 | */ | |
705 | public function __construct(array $keys, $desc='', $required=VALUE_REQUIRED, $default=null) { | |
706 | global $CFG; | |
707 | ||
708 | if ($CFG->debugdeveloper) { | |
709 | foreach ($keys as $key => $value) { | |
710 | if ($value instanceof external_value) { | |
711 | if ($value->required == VALUE_OPTIONAL) { | |
712 | debugging('External function parameters: invalid OPTIONAL value specified.', DEBUG_DEVELOPER); | |
713 | break; | |
714 | } | |
715 | } | |
716 | } | |
717 | } | |
718 | parent::__construct($keys, $desc, $required, $default); | |
719 | } | |
c29cca30 | 720 | } |
2822f40a | 721 | |
4615817d JM |
722 | /** |
723 | * Generate a token | |
724 | * | |
725 | * @param string $tokentype EXTERNAL_TOKEN_EMBEDDED|EXTERNAL_TOKEN_PERMANENT | |
726 | * @param stdClass|int $serviceorid service linked to the token | |
727 | * @param int $userid user linked to the token | |
728 | * @param stdClass|int $contextorid | |
729 | * @param int $validuntil date when the token expired | |
730 | * @param string $iprestriction allowed ip - if 0 or empty then all ips are allowed | |
731 | * @return string generated token | |
732 | * @author 2010 Jamie Pratt | |
733 | * @since Moodle 2.0 | |
734 | */ | |
2822f40a JP |
735 | function external_generate_token($tokentype, $serviceorid, $userid, $contextorid, $validuntil=0, $iprestriction=''){ |
736 | global $DB, $USER; | |
737 | // make sure the token doesn't exist (even if it should be almost impossible with the random generation) | |
738 | $numtries = 0; | |
739 | do { | |
740 | $numtries ++; | |
741 | $generatedtoken = md5(uniqid(rand(),1)); | |
742 | if ($numtries > 5){ | |
743 | throw new moodle_exception('tokengenerationfailed'); | |
744 | } | |
745 | } while ($DB->record_exists('external_tokens', array('token'=>$generatedtoken))); | |
365a5941 | 746 | $newtoken = new stdClass(); |
2822f40a JP |
747 | $newtoken->token = $generatedtoken; |
748 | if (!is_object($serviceorid)){ | |
749 | $service = $DB->get_record('external_services', array('id' => $serviceorid)); | |
750 | } else { | |
751 | $service = $serviceorid; | |
752 | } | |
753 | if (!is_object($contextorid)){ | |
d197ea43 | 754 | $context = context::instance_by_id($contextorid, MUST_EXIST); |
2822f40a JP |
755 | } else { |
756 | $context = $contextorid; | |
757 | } | |
758 | if (empty($service->requiredcapability) || has_capability($service->requiredcapability, $context, $userid)) { | |
759 | $newtoken->externalserviceid = $service->id; | |
760 | } else { | |
761 | throw new moodle_exception('nocapabilitytousethisservice'); | |
762 | } | |
763 | $newtoken->tokentype = $tokentype; | |
764 | $newtoken->userid = $userid; | |
2d0acbd5 JP |
765 | if ($tokentype == EXTERNAL_TOKEN_EMBEDDED){ |
766 | $newtoken->sid = session_id(); | |
767 | } | |
4f0c2d00 PS |
768 | |
769 | $newtoken->contextid = $context->id; | |
2822f40a JP |
770 | $newtoken->creatorid = $USER->id; |
771 | $newtoken->timecreated = time(); | |
772 | $newtoken->validuntil = $validuntil; | |
773 | if (!empty($iprestriction)) { | |
774 | $newtoken->iprestriction = $iprestriction; | |
775 | } | |
956232db DP |
776 | // Generate the private token, it must be transmitted only via https. |
777 | $newtoken->privatetoken = random_string(64); | |
2822f40a JP |
778 | $DB->insert_record('external_tokens', $newtoken); |
779 | return $newtoken->token; | |
2d0acbd5 | 780 | } |
4615817d | 781 | |
2d0acbd5 | 782 | /** |
df997f84 | 783 | * Create and return a session linked token. Token to be used for html embedded client apps that want to communicate |
2d0acbd5 JP |
784 | * with the Moodle server through web services. The token is linked to the current session for the current page request. |
785 | * It is expected this will be called in the script generating the html page that is embedding the client app and that the | |
786 | * returned token will be somehow passed into the client app being embedded in the page. | |
4615817d | 787 | * |
2d0acbd5 JP |
788 | * @param string $servicename name of the web service. Service name as defined in db/services.php |
789 | * @param int $context context within which the web service can operate. | |
790 | * @return int returns token id. | |
4615817d | 791 | * @since Moodle 2.0 |
2d0acbd5 JP |
792 | */ |
793 | function external_create_service_token($servicename, $context){ | |
794 | global $USER, $DB; | |
795 | $service = $DB->get_record('external_services', array('name'=>$servicename), '*', MUST_EXIST); | |
796 | return external_generate_token(EXTERNAL_TOKEN_EMBEDDED, $service, $USER->id, $context, 0); | |
bc81eadb JM |
797 | } |
798 | ||
799 | /** | |
800 | * Delete all pre-built services (+ related tokens) and external functions information defined in the specified component. | |
801 | * | |
802 | * @param string $component name of component (moodle, mod_assignment, etc.) | |
803 | */ | |
804 | function external_delete_descriptions($component) { | |
805 | global $DB; | |
806 | ||
807 | $params = array($component); | |
808 | ||
809 | $DB->delete_records_select('external_tokens', | |
810 | "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params); | |
811 | $DB->delete_records_select('external_services_users', | |
812 | "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params); | |
813 | $DB->delete_records_select('external_services_functions', | |
814 | "functionname IN (SELECT name FROM {external_functions} WHERE component = ?)", $params); | |
815 | $DB->delete_records('external_services', array('component'=>$component)); | |
816 | $DB->delete_records('external_functions', array('component'=>$component)); | |
03d38b92 Y |
817 | } |
818 | ||
819 | /** | |
93ce0e82 | 820 | * Standard Moodle web service warnings |
03d38b92 | 821 | * |
93ce0e82 JM |
822 | * @package core_webservice |
823 | * @copyright 2012 Jerome Mouneyrac | |
824 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
825 | * @since Moodle 2.3 | |
826 | */ | |
827 | class external_warnings extends external_multiple_structure { | |
828 | ||
829 | /** | |
830 | * Constructor | |
831 | * | |
832 | * @since Moodle 2.3 | |
833 | */ | |
8118dbd0 JM |
834 | public function __construct($itemdesc = 'item', $itemiddesc = 'item id', |
835 | $warningcodedesc = 'the warning code can be used by the client app to implement specific behaviour') { | |
93ce0e82 JM |
836 | |
837 | parent::__construct( | |
838 | new external_single_structure( | |
839 | array( | |
8118dbd0 JM |
840 | 'item' => new external_value(PARAM_TEXT, $itemdesc, VALUE_OPTIONAL), |
841 | 'itemid' => new external_value(PARAM_INT, $itemiddesc, VALUE_OPTIONAL), | |
842 | 'warningcode' => new external_value(PARAM_ALPHANUM, $warningcodedesc), | |
93ce0e82 JM |
843 | 'message' => new external_value(PARAM_TEXT, |
844 | 'untranslated english message to explain the warning') | |
845 | ), 'warning'), | |
846 | 'list of warnings', VALUE_OPTIONAL); | |
847 | } | |
848 | } | |
849 | ||
850 | /** | |
851 | * A pre-filled external_value class for text format. | |
852 | * | |
853 | * Default is FORMAT_HTML | |
854 | * This should be used all the time in external xxx_params()/xxx_returns functions | |
855 | * as it is the standard way to implement text format param/return values. | |
856 | * | |
857 | * @package core_webservice | |
858 | * @copyright 2012 Jerome Mouneyrac | |
859 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
860 | * @since Moodle 2.3 | |
03d38b92 | 861 | */ |
93ce0e82 JM |
862 | class external_format_value extends external_value { |
863 | ||
864 | /** | |
865 | * Constructor | |
866 | * | |
867 | * @param string $textfieldname Name of the text field | |
868 | * @param int $required if VALUE_REQUIRED then set standard default FORMAT_HTML | |
14968ca9 | 869 | * @param int $default Default value. |
93ce0e82 JM |
870 | * @since Moodle 2.3 |
871 | */ | |
14968ca9 | 872 | public function __construct($textfieldname, $required = VALUE_REQUIRED, $default = null) { |
93ce0e82 | 873 | |
14968ca9 DW |
874 | if ($default == null && $required == VALUE_DEFAULT) { |
875 | $default = FORMAT_HTML; | |
876 | } | |
93ce0e82 JM |
877 | |
878 | $desc = $textfieldname . ' format (' . FORMAT_HTML . ' = HTML, ' | |
879 | . FORMAT_MOODLE . ' = MOODLE, ' | |
880 | . FORMAT_PLAIN . ' = PLAIN or ' | |
881 | . FORMAT_MARKDOWN . ' = MARKDOWN)'; | |
882 | ||
55e168e3 | 883 | parent::__construct(PARAM_INT, $desc, $required, $default); |
93ce0e82 JM |
884 | } |
885 | } | |
886 | ||
887 | /** | |
888 | * Validate text field format against known FORMAT_XXX | |
889 | * | |
890 | * @param array $format the format to validate | |
891 | * @return the validated format | |
892 | * @throws coding_exception | |
5bcfd504 | 893 | * @since Moodle 2.3 |
93ce0e82 JM |
894 | */ |
895 | function external_validate_format($format) { | |
896 | $allowedformats = array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN); | |
897 | if (!in_array($format, $allowedformats)) { | |
898 | throw new moodle_exception('formatnotsupported', 'webservice', '' , null, | |
899 | 'The format with value=' . $format . ' is not supported by this Moodle site'); | |
900 | } | |
901 | return $format; | |
902 | } | |
903 | ||
9764aab9 DW |
904 | /** |
905 | * Format the string to be returned properly as requested by the either the web service server, | |
906 | * either by an internally call. | |
907 | * The caller can change the format (raw) with the external_settings singleton | |
908 | * All web service servers must set this singleton when parsing the $_GET and $_POST. | |
909 | * | |
1d014075 FM |
910 | * <pre> |
911 | * Options are the same that in {@link format_string()} with some changes: | |
912 | * filter : Can be set to false to force filters off, else observes {@link external_settings}. | |
913 | * </pre> | |
914 | * | |
9764aab9 DW |
915 | * @param string $str The string to be filtered. Should be plain text, expect |
916 | * possibly for multilang tags. | |
917 | * @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713 | |
0377e78e | 918 | * @param context|int $contextorid The id of the context for the string or the context (affects filters). |
9764aab9 DW |
919 | * @param array $options options array/object or courseid |
920 | * @return string text | |
921 | * @since Moodle 3.0 | |
922 | */ | |
0377e78e | 923 | function external_format_string($str, $contextorid, $striplinks = true, $options = array()) { |
9764aab9 DW |
924 | |
925 | // Get settings (singleton). | |
926 | $settings = external_settings::get_instance(); | |
0377e78e | 927 | if (empty($contextorid)) { |
9764aab9 DW |
928 | throw new coding_exception('contextid is required'); |
929 | } | |
930 | ||
931 | if (!$settings->get_raw()) { | |
0377e78e RW |
932 | if (is_object($contextorid) && is_a($contextorid, 'context')) { |
933 | $context = $contextorid; | |
934 | } else { | |
935 | $context = context::instance_by_id($contextorid); | |
936 | } | |
9764aab9 | 937 | $options['context'] = $context; |
1d014075 | 938 | $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter(); |
9764aab9 DW |
939 | $str = format_string($str, $striplinks, $options); |
940 | } | |
941 | ||
942 | return $str; | |
943 | } | |
944 | ||
93ce0e82 JM |
945 | /** |
946 | * Format the text to be returned properly as requested by the either the web service server, | |
947 | * either by an internally call. | |
948 | * The caller can change the format (raw, filter, file, fileurl) with the external_settings singleton | |
949 | * All web service servers must set this singleton when parsing the $_GET and $_POST. | |
950 | * | |
19a131ed PFO |
951 | * <pre> |
952 | * Options are the same that in {@link format_text()} with some changes in defaults to provide backwards compatibility: | |
953 | * trusted : If true the string won't be cleaned. Default false. | |
954 | * noclean : If true the string won't be cleaned only if trusted is also true. Default false. | |
955 | * nocache : If true the string will not be cached and will be formatted every call. Default false. | |
1d014075 | 956 | * filter : Can be set to false to force filters off, else observes {@link external_settings}. |
19a131ed PFO |
957 | * para : If true then the returned string will be wrapped in div tags. Default (different from format_text) false. |
958 | * Default changed because div tags are not commonly needed. | |
959 | * newlines : If true then lines newline breaks will be converted to HTML newline breaks. Default true. | |
960 | * context : Not used! Using contextid parameter instead. | |
961 | * overflowdiv : If set to true the formatted text will be encased in a div with the class no-overflow before being | |
962 | * returned. Default false. | |
963 | * allowid : If true then id attributes will not be removed, even when using htmlpurifier. Default (different from | |
964 | * format_text) true. Default changed id attributes are commonly needed. | |
977804ed | 965 | * blanktarget : If true all <a> tags will have target="_blank" added unless target is explicitly specified. |
19a131ed PFO |
966 | * </pre> |
967 | * | |
93ce0e82 | 968 | * @param string $text The content that may contain ULRs in need of rewriting. |
c4a15021 | 969 | * @param int $textformat The text format. |
0377e78e | 970 | * @param context|int $contextorid This parameter and the next two identify the file area to use. |
93ce0e82 JM |
971 | * @param string $component |
972 | * @param string $filearea helps identify the file area. | |
973 | * @param int $itemid helps identify the file area. | |
19a131ed | 974 | * @param object/array $options text formatting options |
93ce0e82 JM |
975 | * @return array text + textformat |
976 | * @since Moodle 2.3 | |
833be5e4 | 977 | * @since Moodle 3.2 component, filearea and itemid are optional parameters |
93ce0e82 | 978 | */ |
0377e78e | 979 | function external_format_text($text, $textformat, $contextorid, $component = null, $filearea = null, $itemid = null, |
833be5e4 | 980 | $options = null) { |
93ce0e82 JM |
981 | global $CFG; |
982 | ||
983 | // Get settings (singleton). | |
984 | $settings = external_settings::get_instance(); | |
985 | ||
0377e78e RW |
986 | if (is_object($contextorid) && is_a($contextorid, 'context')) { |
987 | $context = $contextorid; | |
988 | $contextid = $context->id; | |
989 | } else { | |
990 | $context = null; | |
991 | $contextid = $contextorid; | |
992 | } | |
993 | ||
833be5e4 | 994 | if ($component and $filearea and $settings->get_fileurl()) { |
ea29059e | 995 | require_once($CFG->libdir . "/filelib.php"); |
93ce0e82 JM |
996 | $text = file_rewrite_pluginfile_urls($text, $settings->get_file(), $contextid, $component, $filearea, $itemid); |
997 | } | |
998 | ||
4b82c15c DM |
999 | // Note that $CFG->forceclean does not apply here if the client requests for the raw database content. |
1000 | // This is consistent with web clients that are still able to load non-cleaned text into editors, too. | |
1001 | ||
93ce0e82 | 1002 | if (!$settings->get_raw()) { |
19a131ed PFO |
1003 | $options = (array)$options; |
1004 | ||
1005 | // If context is passed in options, check that is the same to show a debug message. | |
1006 | if (isset($options['context'])) { | |
1007 | if ((is_object($options['context']) && $options['context']->id != $contextid) | |
b1a9804a | 1008 | || (!is_object($options['context']) && $options['context'] != $contextid)) { |
19a131ed PFO |
1009 | debugging('Different contexts found in external_format_text parameters. $options[\'context\'] not allowed. |
1010 | Using $contextid parameter...', DEBUG_DEVELOPER); | |
1011 | } | |
1012 | } | |
1013 | ||
1d014075 | 1014 | $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter(); |
19a131ed | 1015 | $options['para'] = isset($options['para']) ? $options['para'] : false; |
0377e78e | 1016 | $options['context'] = !is_null($context) ? $context : context::instance_by_id($contextid); |
19a131ed PFO |
1017 | $options['allowid'] = isset($options['allowid']) ? $options['allowid'] : true; |
1018 | ||
1019 | $text = format_text($text, $textformat, $options); | |
c4a15021 | 1020 | $textformat = FORMAT_HTML; // Once converted to html (from markdown, plain... lets inform consumer this is already HTML). |
93ce0e82 JM |
1021 | } |
1022 | ||
1023 | return array($text, $textformat); | |
1024 | } | |
1025 | ||
0a90f624 JL |
1026 | /** |
1027 | * Generate or return an existing token for the current authenticated user. | |
1028 | * This function is used for creating a valid token for users authenticathing via login/token.php or admin/tool/mobile/launch.php. | |
1029 | * | |
1030 | * @param stdClass $service external service object | |
1031 | * @return stdClass token object | |
1032 | * @since Moodle 3.2 | |
1033 | * @throws moodle_exception | |
1034 | */ | |
1035 | function external_generate_token_for_current_user($service) { | |
993e8175 | 1036 | global $DB, $USER, $CFG; |
0a90f624 JL |
1037 | |
1038 | core_user::require_active_user($USER, true, true); | |
1039 | ||
1040 | // Check if there is any required system capability. | |
1041 | if ($service->requiredcapability and !has_capability($service->requiredcapability, context_system::instance())) { | |
1042 | throw new moodle_exception('missingrequiredcapability', 'webservice', '', $service->requiredcapability); | |
1043 | } | |
1044 | ||
1045 | // Specific checks related to user restricted service. | |
1046 | if ($service->restrictedusers) { | |
1047 | $authoriseduser = $DB->get_record('external_services_users', | |
1048 | array('externalserviceid' => $service->id, 'userid' => $USER->id)); | |
1049 | ||
1050 | if (empty($authoriseduser)) { | |
1051 | throw new moodle_exception('usernotallowed', 'webservice', '', $service->shortname); | |
1052 | } | |
1053 | ||
1054 | if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) { | |
1055 | throw new moodle_exception('invalidtimedtoken', 'webservice'); | |
1056 | } | |
1057 | ||
1058 | if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) { | |
1059 | throw new moodle_exception('invalidiptoken', 'webservice'); | |
1060 | } | |
1061 | } | |
1062 | ||
1063 | // Check if a token has already been created for this user and this service. | |
1064 | $conditions = array( | |
1065 | 'userid' => $USER->id, | |
1066 | 'externalserviceid' => $service->id, | |
1067 | 'tokentype' => EXTERNAL_TOKEN_PERMANENT | |
1068 | ); | |
1069 | $tokens = $DB->get_records('external_tokens', $conditions, 'timecreated ASC'); | |
1070 | ||
1071 | // A bit of sanity checks. | |
1072 | foreach ($tokens as $key => $token) { | |
1073 | ||
1074 | // Checks related to a specific token. (script execution continue). | |
1075 | $unsettoken = false; | |
1076 | // If sid is set then there must be a valid associated session no matter the token type. | |
1077 | if (!empty($token->sid)) { | |
1078 | if (!\core\session\manager::session_exists($token->sid)) { | |
1079 | // This token will never be valid anymore, delete it. | |
1080 | $DB->delete_records('external_tokens', array('sid' => $token->sid)); | |
1081 | $unsettoken = true; | |
1082 | } | |
1083 | } | |
1084 | ||
1085 | // Remove token is not valid anymore. | |
1086 | if (!empty($token->validuntil) and $token->validuntil < time()) { | |
1087 | $DB->delete_records('external_tokens', array('token' => $token->token, 'tokentype' => EXTERNAL_TOKEN_PERMANENT)); | |
1088 | $unsettoken = true; | |
1089 | } | |
1090 | ||
1091 | // Remove token if its ip not in whitelist. | |
1092 | if (isset($token->iprestriction) and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { | |
1093 | $unsettoken = true; | |
1094 | } | |
1095 | ||
1096 | if ($unsettoken) { | |
1097 | unset($tokens[$key]); | |
1098 | } | |
1099 | } | |
1100 | ||
1101 | // If some valid tokens exist then use the most recent. | |
1102 | if (count($tokens) > 0) { | |
1103 | $token = array_pop($tokens); | |
1104 | } else { | |
1105 | $context = context_system::instance(); | |
1106 | $isofficialservice = $service->shortname == MOODLE_OFFICIAL_MOBILE_SERVICE; | |
1107 | ||
1108 | if (($isofficialservice and has_capability('moodle/webservice:createmobiletoken', $context)) or | |
1109 | (!is_siteadmin($USER) && has_capability('moodle/webservice:createtoken', $context))) { | |
1110 | ||
1111 | // Create a new token. | |
1112 | $token = new stdClass; | |
1113 | $token->token = md5(uniqid(rand(), 1)); | |
1114 | $token->userid = $USER->id; | |
1115 | $token->tokentype = EXTERNAL_TOKEN_PERMANENT; | |
1116 | $token->contextid = context_system::instance()->id; | |
1117 | $token->creatorid = $USER->id; | |
1118 | $token->timecreated = time(); | |
1119 | $token->externalserviceid = $service->id; | |
993e8175 JL |
1120 | // By default tokens are valid for 12 weeks. |
1121 | $token->validuntil = $token->timecreated + $CFG->tokenduration; | |
56c84137 JL |
1122 | $token->iprestriction = null; |
1123 | $token->sid = null; | |
1124 | $token->lastaccess = null; | |
69cbe359 JL |
1125 | // Generate the private token, it must be transmitted only via https. |
1126 | $token->privatetoken = random_string(64); | |
0a90f624 JL |
1127 | $token->id = $DB->insert_record('external_tokens', $token); |
1128 | ||
56c84137 JL |
1129 | $eventtoken = clone $token; |
1130 | $eventtoken->privatetoken = null; | |
0a90f624 | 1131 | $params = array( |
56c84137 | 1132 | 'objectid' => $eventtoken->id, |
0a90f624 JL |
1133 | 'relateduserid' => $USER->id, |
1134 | 'other' => array( | |
1135 | 'auto' => true | |
1136 | ) | |
1137 | ); | |
1138 | $event = \core\event\webservice_token_created::create($params); | |
56c84137 | 1139 | $event->add_record_snapshot('external_tokens', $eventtoken); |
0a90f624 JL |
1140 | $event->trigger(); |
1141 | } else { | |
1142 | throw new moodle_exception('cannotcreatetoken', 'webservice', '', $service->shortname); | |
1143 | } | |
1144 | } | |
1145 | return $token; | |
1146 | } | |
1147 | ||
a947ecd6 JL |
1148 | /** |
1149 | * Set the last time a token was sent and trigger the \core\event\webservice_token_sent event. | |
1150 | * | |
1151 | * This function is used when a token is generated by the user via login/token.php or admin/tool/mobile/launch.php. | |
1152 | * In order to protect the privatetoken, we remove it from the event params. | |
1153 | * | |
1154 | * @param stdClass $token token object | |
1155 | * @since Moodle 3.2 | |
1156 | */ | |
1157 | function external_log_token_request($token) { | |
1158 | global $DB; | |
1159 | ||
1160 | $token->privatetoken = null; | |
1161 | ||
1162 | // Log token access. | |
1163 | $DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id)); | |
1164 | ||
1165 | $params = array( | |
1166 | 'objectid' => $token->id, | |
1167 | ); | |
1168 | $event = \core\event\webservice_token_sent::create($params); | |
1169 | $event->add_record_snapshot('external_tokens', $token); | |
1170 | $event->trigger(); | |
1171 | } | |
0a90f624 | 1172 | |
93ce0e82 JM |
1173 | /** |
1174 | * Singleton to handle the external settings. | |
1175 | * | |
1176 | * We use singleton to encapsulate the "logic" | |
1177 | * | |
1178 | * @package core_webservice | |
1179 | * @copyright 2012 Jerome Mouneyrac | |
1180 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1181 | * @since Moodle 2.3 | |
1182 | */ | |
1183 | class external_settings { | |
1184 | ||
1185 | /** @var object the singleton instance */ | |
1186 | public static $instance = null; | |
1187 | ||
1188 | /** @var boolean Should the external function return raw text or formatted */ | |
1189 | private $raw = false; | |
1190 | ||
1191 | /** @var boolean Should the external function filter the text */ | |
1192 | private $filter = false; | |
1193 | ||
1194 | /** @var boolean Should the external function rewrite plugin file url */ | |
1195 | private $fileurl = true; | |
1196 | ||
1197 | /** @var string In which file should the urls be rewritten */ | |
1198 | private $file = 'webservice/pluginfile.php'; | |
1199 | ||
bb14a488 JL |
1200 | /** @var string The session lang */ |
1201 | private $lang = ''; | |
1202 | ||
93ce0e82 JM |
1203 | /** |
1204 | * Constructor - protected - can not be instanciated | |
1205 | */ | |
1206 | protected function __construct() { | |
34773ccf | 1207 | if ((AJAX_SCRIPT == false) && (CLI_SCRIPT == false) && (WS_SERVER == false)) { |
9764aab9 DW |
1208 | // For normal pages, the default should match the default for format_text. |
1209 | $this->filter = true; | |
1d772481 AA |
1210 | // Use pluginfile.php for web requests. |
1211 | $this->file = 'pluginfile.php'; | |
9764aab9 | 1212 | } |
93ce0e82 JM |
1213 | } |
1214 | ||
1215 | /** | |
1216 | * Clone - private - can not be cloned | |
1217 | */ | |
1218 | private final function __clone() { | |
1219 | } | |
1220 | ||
1221 | /** | |
1222 | * Return only one instance | |
1223 | * | |
9743b2d4 | 1224 | * @return \external_settings |
93ce0e82 JM |
1225 | */ |
1226 | public static function get_instance() { | |
1227 | if (self::$instance === null) { | |
1228 | self::$instance = new external_settings; | |
1229 | } | |
1230 | ||
1231 | return self::$instance; | |
1232 | } | |
1233 | ||
1234 | /** | |
1235 | * Set raw | |
1236 | * | |
1237 | * @param boolean $raw | |
1238 | */ | |
1239 | public function set_raw($raw) { | |
1240 | $this->raw = $raw; | |
1241 | } | |
1242 | ||
1243 | /** | |
1244 | * Get raw | |
1245 | * | |
1246 | * @return boolean | |
1247 | */ | |
1248 | public function get_raw() { | |
1249 | return $this->raw; | |
1250 | } | |
1251 | ||
1252 | /** | |
1253 | * Set filter | |
1254 | * | |
1255 | * @param boolean $filter | |
1256 | */ | |
1257 | public function set_filter($filter) { | |
1258 | $this->filter = $filter; | |
1259 | } | |
1260 | ||
1261 | /** | |
1262 | * Get filter | |
1263 | * | |
1264 | * @return boolean | |
1265 | */ | |
1266 | public function get_filter() { | |
1267 | return $this->filter; | |
1268 | } | |
1269 | ||
1270 | /** | |
1271 | * Set fileurl | |
1272 | * | |
1273 | * @param boolean $fileurl | |
1274 | */ | |
1275 | public function set_fileurl($fileurl) { | |
1276 | $this->fileurl = $fileurl; | |
1277 | } | |
1278 | ||
1279 | /** | |
1280 | * Get fileurl | |
1281 | * | |
1282 | * @return boolean | |
1283 | */ | |
1284 | public function get_fileurl() { | |
1285 | return $this->fileurl; | |
1286 | } | |
1287 | ||
1288 | /** | |
1289 | * Set file | |
1290 | * | |
1291 | * @param string $file | |
1292 | */ | |
1293 | public function set_file($file) { | |
1294 | $this->file = $file; | |
1295 | } | |
1296 | ||
1297 | /** | |
1298 | * Get file | |
1299 | * | |
1300 | * @return string | |
1301 | */ | |
1302 | public function get_file() { | |
1303 | return $this->file; | |
1304 | } | |
bb14a488 JL |
1305 | |
1306 | /** | |
1307 | * Set lang | |
1308 | * | |
1309 | * @param string $lang | |
1310 | */ | |
1311 | public function set_lang($lang) { | |
1312 | $this->lang = $lang; | |
1313 | } | |
1314 | ||
1315 | /** | |
1316 | * Get lang | |
1317 | * | |
1318 | * @return string | |
1319 | */ | |
1320 | public function get_lang() { | |
1321 | return $this->lang; | |
1322 | } | |
50f9449f | 1323 | } |
82f0f5e3 JL |
1324 | |
1325 | /** | |
1326 | * Utility functions for the external API. | |
1327 | * | |
1328 | * @package core_webservice | |
1329 | * @copyright 2015 Juan Leyva | |
1330 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1331 | * @since Moodle 3.0 | |
1332 | */ | |
1333 | class external_util { | |
1334 | ||
1335 | /** | |
1336 | * Validate a list of courses, returning the complete course objects for valid courses. | |
1337 | * | |
107ab4b9 | 1338 | * Each course has an additional 'contextvalidated' field, this will be set to true unless |
1339 | * you set $keepfails, in which case it will be false if validation fails for a course. | |
1340 | * | |
82f0f5e3 | 1341 | * @param array $courseids A list of course ids |
b80ede13 | 1342 | * @param array $courses An array of courses already pre-fetched, indexed by course id. |
e2002009 | 1343 | * @param bool $addcontext True if the returned course object should include the full context object. |
107ab4b9 | 1344 | * @param bool $keepfails True to keep all the course objects even if validation fails |
82f0f5e3 JL |
1345 | * @return array An array of courses and the validation warnings |
1346 | */ | |
107ab4b9 | 1347 | public static function validate_courses($courseids, $courses = array(), $addcontext = false, |
1348 | $keepfails = false) { | |
1349 | global $DB; | |
1350 | ||
82f0f5e3 JL |
1351 | // Delete duplicates. |
1352 | $courseids = array_unique($courseids); | |
82f0f5e3 JL |
1353 | $warnings = array(); |
1354 | ||
4b11af96 | 1355 | // Remove courses which are not even requested. |
107ab4b9 | 1356 | $courses = array_intersect_key($courses, array_flip($courseids)); |
1357 | ||
1358 | // For any courses NOT loaded already, get them in a single query (and preload contexts) | |
1359 | // for performance. Preserve ordering because some tests depend on it. | |
1360 | $newcourseids = []; | |
1361 | foreach ($courseids as $cid) { | |
1362 | if (!array_key_exists($cid, $courses)) { | |
1363 | $newcourseids[] = $cid; | |
1364 | } | |
1365 | } | |
1366 | if ($newcourseids) { | |
1367 | list ($listsql, $listparams) = $DB->get_in_or_equal($newcourseids); | |
1368 | ||
1369 | // Load list of courses, and preload associated contexts. | |
1370 | $contextselect = context_helper::get_preload_record_columns_sql('x'); | |
1371 | $newcourses = $DB->get_records_sql(" | |
1372 | SELECT c.*, $contextselect | |
1373 | FROM {course} c | |
65b10696 | 1374 | JOIN {context} x ON x.instanceid = c.id |
107ab4b9 | 1375 | WHERE x.contextlevel = ? AND c.id $listsql", |
1376 | array_merge([CONTEXT_COURSE], $listparams)); | |
1377 | foreach ($newcourseids as $cid) { | |
1378 | if (array_key_exists($cid, $newcourses)) { | |
1379 | $course = $newcourses[$cid]; | |
1380 | context_helper::preload_from_record($course); | |
1381 | $courses[$course->id] = $course; | |
1382 | } | |
1383 | } | |
1384 | } | |
4b11af96 | 1385 | |
82f0f5e3 JL |
1386 | foreach ($courseids as $cid) { |
1387 | // Check the user can function in this context. | |
1388 | try { | |
1389 | $context = context_course::instance($cid); | |
1390 | external_api::validate_context($context); | |
4b11af96 | 1391 | |
e2002009 JL |
1392 | if ($addcontext) { |
1393 | $courses[$cid]->context = $context; | |
1394 | } | |
107ab4b9 | 1395 | $courses[$cid]->contextvalidated = true; |
82f0f5e3 | 1396 | } catch (Exception $e) { |
107ab4b9 | 1397 | if ($keepfails) { |
1398 | $courses[$cid]->contextvalidated = false; | |
1399 | } else { | |
1400 | unset($courses[$cid]); | |
1401 | } | |
82f0f5e3 JL |
1402 | $warnings[] = array( |
1403 | 'item' => 'course', | |
1404 | 'itemid' => $cid, | |
1405 | 'warningcode' => '1', | |
1406 | 'message' => 'No access rights in course context' | |
1407 | ); | |
1408 | } | |
1409 | } | |
1410 | ||
1411 | return array($courses, $warnings); | |
1412 | } | |
1413 | ||
ae584b3b JL |
1414 | /** |
1415 | * Returns all area files (optionally limited by itemid). | |
1416 | * | |
1417 | * @param int $contextid context ID | |
1418 | * @param string $component component | |
1419 | * @param string $filearea file area | |
1420 | * @param int $itemid item ID or all files if not specified | |
1421 | * @param bool $useitemidinurl wether to use the item id in the file URL (modules intro don't use it) | |
1422 | * @return array of files, compatible with the external_files structure. | |
1423 | * @since Moodle 3.2 | |
1424 | */ | |
1425 | public static function get_area_files($contextid, $component, $filearea, $itemid = false, $useitemidinurl = true) { | |
1426 | $files = array(); | |
1427 | $fs = get_file_storage(); | |
1428 | ||
1429 | if ($areafiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'itemid, filepath, filename', false)) { | |
1430 | foreach ($areafiles as $areafile) { | |
1431 | $file = array(); | |
1432 | $file['filename'] = $areafile->get_filename(); | |
1433 | $file['filepath'] = $areafile->get_filepath(); | |
1434 | $file['mimetype'] = $areafile->get_mimetype(); | |
1435 | $file['filesize'] = $areafile->get_filesize(); | |
1436 | $file['timemodified'] = $areafile->get_timemodified(); | |
1104a9fa JL |
1437 | $file['isexternalfile'] = $areafile->is_external_file(); |
1438 | if ($file['isexternalfile']) { | |
1439 | $file['repositorytype'] = $areafile->get_repository_type(); | |
1440 | } | |
ae584b3b JL |
1441 | $fileitemid = $useitemidinurl ? $areafile->get_itemid() : null; |
1442 | $file['fileurl'] = moodle_url::make_webservice_pluginfile_url($contextid, $component, $filearea, | |
1443 | $fileitemid, $areafile->get_filepath(), $areafile->get_filename())->out(false); | |
1444 | $files[] = $file; | |
1445 | } | |
1446 | } | |
1447 | return $files; | |
1448 | } | |
1449 | } | |
1450 | ||
1451 | /** | |
1452 | * External structure representing a set of files. | |
1453 | * | |
1454 | * @package core_webservice | |
1455 | * @copyright 2016 Juan Leyva | |
1456 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1457 | * @since Moodle 3.2 | |
1458 | */ | |
1459 | class external_files extends external_multiple_structure { | |
1460 | ||
1461 | /** | |
1462 | * Constructor | |
1463 | * @param string $desc Description for the multiple structure. | |
1464 | * @param int $required The type of value (VALUE_REQUIRED OR VALUE_OPTIONAL). | |
1465 | */ | |
1466 | public function __construct($desc = 'List of files.', $required = VALUE_REQUIRED) { | |
1467 | ||
1468 | parent::__construct( | |
1469 | new external_single_structure( | |
1470 | array( | |
1471 | 'filename' => new external_value(PARAM_FILE, 'File name.', VALUE_OPTIONAL), | |
1472 | 'filepath' => new external_value(PARAM_PATH, 'File path.', VALUE_OPTIONAL), | |
1473 | 'filesize' => new external_value(PARAM_INT, 'File size.', VALUE_OPTIONAL), | |
1474 | 'fileurl' => new external_value(PARAM_URL, 'Downloadable file url.', VALUE_OPTIONAL), | |
1475 | 'timemodified' => new external_value(PARAM_INT, 'Time modified.', VALUE_OPTIONAL), | |
1476 | 'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL), | |
1104a9fa JL |
1477 | 'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.', VALUE_OPTIONAL), |
1478 | 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.', VALUE_OPTIONAL), | |
ae584b3b JL |
1479 | ), |
1480 | 'File.' | |
1481 | ), | |
1482 | $desc, | |
1483 | $required | |
1484 | ); | |
1485 | } | |
44acbb81 JL |
1486 | |
1487 | /** | |
1488 | * Return the properties ready to be used by an exporter. | |
1489 | * | |
1490 | * @return array properties | |
1491 | * @since Moodle 3.3 | |
1492 | */ | |
1493 | public static function get_properties_for_exporter() { | |
1494 | return [ | |
1495 | 'filename' => array( | |
1496 | 'type' => PARAM_FILE, | |
1497 | 'description' => 'File name.', | |
1498 | 'optional' => true, | |
1499 | 'null' => NULL_NOT_ALLOWED, | |
1500 | ), | |
1501 | 'filepath' => array( | |
1502 | 'type' => PARAM_PATH, | |
1503 | 'description' => 'File path.', | |
1504 | 'optional' => true, | |
1505 | 'null' => NULL_NOT_ALLOWED, | |
1506 | ), | |
1507 | 'filesize' => array( | |
1508 | 'type' => PARAM_INT, | |
1509 | 'description' => 'File size.', | |
1510 | 'optional' => true, | |
1511 | 'null' => NULL_NOT_ALLOWED, | |
1512 | ), | |
1513 | 'fileurl' => array( | |
1514 | 'type' => PARAM_URL, | |
1515 | 'description' => 'Downloadable file url.', | |
1516 | 'optional' => true, | |
1517 | 'null' => NULL_NOT_ALLOWED, | |
1518 | ), | |
1519 | 'timemodified' => array( | |
1520 | 'type' => PARAM_INT, | |
1521 | 'description' => 'Time modified.', | |
1522 | 'optional' => true, | |
1523 | 'null' => NULL_NOT_ALLOWED, | |
1524 | ), | |
1525 | 'mimetype' => array( | |
1526 | 'type' => PARAM_RAW, | |
1527 | 'description' => 'File mime type.', | |
1528 | 'optional' => true, | |
1529 | 'null' => NULL_NOT_ALLOWED, | |
1530 | ), | |
1104a9fa JL |
1531 | 'isexternalfile' => array( |
1532 | 'type' => PARAM_BOOL, | |
1533 | 'description' => 'Whether is an external file.', | |
1534 | 'optional' => true, | |
1535 | 'null' => NULL_NOT_ALLOWED, | |
1536 | ), | |
1537 | 'repositorytype' => array( | |
1538 | 'type' => PARAM_PLUGIN, | |
1539 | 'description' => 'The repository type for the external files.', | |
1540 | 'optional' => true, | |
1541 | 'null' => NULL_ALLOWED, | |
1542 | ), | |
44acbb81 JL |
1543 | ]; |
1544 | } | |
82f0f5e3 | 1545 | } |