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 | |
5593d2dc | 28 | /** |
bff11d29 | 29 | * Returns detailed function information |
4615817d | 30 | * |
5593d2dc | 31 | * @param string|object $function name of external function or record from external_function |
32 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; | |
33 | * MUST_EXIST means throw exception if no record or multiple records found | |
4615817d JM |
34 | * @return stdClass description or false if not found or exception thrown |
35 | * @since Moodle 2.0 | |
5593d2dc | 36 | */ |
37 | function external_function_info($function, $strictness=MUST_EXIST) { | |
38 | global $DB, $CFG; | |
39 | ||
40 | if (!is_object($function)) { | |
41 | if (!$function = $DB->get_record('external_functions', array('name'=>$function), '*', $strictness)) { | |
42 | return false; | |
43 | } | |
44 | } | |
45 | ||
ed57d984 PŠ |
46 | // First try class autoloading. |
47 | if (!class_exists($function->classname)) { | |
48 | // Fallback to explicit include of externallib.php. | |
49 | $function->classpath = empty($function->classpath) ? core_component::get_component_directory($function->component).'/externallib.php' : $CFG->dirroot.'/'.$function->classpath; | |
50 | if (!file_exists($function->classpath)) { | |
1dc4dc98 | 51 | throw new coding_exception('Cannot find file with external function implementation: ' . $function->classname); |
ed57d984 PŠ |
52 | } |
53 | require_once($function->classpath); | |
54 | if (!class_exists($function->classname)) { | |
55 | throw new coding_exception('Cannot find external class'); | |
56 | } | |
5593d2dc | 57 | } |
5593d2dc | 58 | |
72f8324e | 59 | $function->ajax_method = $function->methodname.'_is_allowed_from_ajax'; |
5593d2dc | 60 | $function->parameters_method = $function->methodname.'_parameters'; |
61 | $function->returns_method = $function->methodname.'_returns'; | |
3c1aa6fd | 62 | $function->deprecated_method = $function->methodname.'_is_deprecated'; |
5593d2dc | 63 | |
64 | // make sure the implementaion class is ok | |
65 | if (!method_exists($function->classname, $function->methodname)) { | |
203fda8a | 66 | throw new coding_exception('Missing implementation method of '.$function->classname.'::'.$function->methodname); |
5593d2dc | 67 | } |
68 | if (!method_exists($function->classname, $function->parameters_method)) { | |
69 | throw new coding_exception('Missing parameters description'); | |
70 | } | |
71 | if (!method_exists($function->classname, $function->returns_method)) { | |
72 | throw new coding_exception('Missing returned values description'); | |
73 | } | |
3c1aa6fd DM |
74 | if (method_exists($function->classname, $function->deprecated_method)) { |
75 | if (call_user_func(array($function->classname, $function->deprecated_method)) === true) { | |
76 | $function->deprecated = true; | |
77 | } | |
78 | } | |
72f8324e | 79 | $function->allowed_from_ajax = false; |
5593d2dc | 80 | |
81 | // fetch the parameters description | |
82 | $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method)); | |
83 | if (!($function->parameters_desc instanceof external_function_parameters)) { | |
84 | throw new coding_exception('Invalid parameters description'); | |
85 | } | |
86 | ||
87 | // fetch the return values description | |
88 | $function->returns_desc = call_user_func(array($function->classname, $function->returns_method)); | |
89 | // null means void result or result is ignored | |
90 | if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) { | |
91 | throw new coding_exception('Invalid return description'); | |
92 | } | |
93 | ||
94 | //now get the function description | |
4615817d | 95 | //TODO MDL-31115 use localised lang pack descriptions, it would be nice to have |
23e7b7cc | 96 | // easy to understand descriptions in admin UI, |
5593d2dc | 97 | // on the other hand this is still a bit in a flux and we need to find some new naming |
98 | // conventions for these descriptions in lang packs | |
99 | $function->description = null; | |
b0d1d941 | 100 | $servicesfile = core_component::get_component_directory($function->component).'/db/services.php'; |
5593d2dc | 101 | if (file_exists($servicesfile)) { |
102 | $functions = null; | |
103 | include($servicesfile); | |
104 | if (isset($functions[$function->name]['description'])) { | |
105 | $function->description = $functions[$function->name]['description']; | |
106 | } | |
048f3d32 TH |
107 | if (isset($functions[$function->name]['testclientpath'])) { |
108 | $function->testclientpath = $functions[$function->name]['testclientpath']; | |
109 | } | |
5d8c1987 DW |
110 | if (isset($functions[$function->name]['type'])) { |
111 | $function->type = $functions[$function->name]['type']; | |
112 | } | |
ba224fb4 DW |
113 | if (isset($functions[$function->name]['ajax'])) { |
114 | $function->allowed_from_ajax = $functions[$function->name]['ajax']; | |
88982b71 DW |
115 | } else if (method_exists($function->classname, $function->ajax_method)) { |
116 | if (call_user_func(array($function->classname, $function->ajax_method)) === true) { | |
117 | debugging('External function ' . $function->ajax_method . '() function is deprecated.' . | |
118 | 'Set ajax=>true in db/service.php instead.', DEBUG_DEVELOPER); | |
119 | $function->allowed_from_ajax = true; | |
120 | } | |
ba224fb4 | 121 | } |
5d8c1987 DW |
122 | if (isset($functions[$function->name]['loginrequired'])) { |
123 | $function->loginrequired = $functions[$function->name]['loginrequired']; | |
124 | } else { | |
125 | $function->loginrequired = true; | |
126 | } | |
5593d2dc | 127 | } |
128 | ||
129 | return $function; | |
130 | } | |
131 | ||
9a0df45a | 132 | /** |
4615817d JM |
133 | * Exception indicating user is not allowed to use external function in the current context. |
134 | * | |
135 | * @package core_webservice | |
136 | * @copyright 2009 Petr Skodak | |
137 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
138 | * @since Moodle 2.0 | |
9a0df45a | 139 | */ |
140 | class restricted_context_exception extends moodle_exception { | |
141 | /** | |
142 | * Constructor | |
4615817d JM |
143 | * |
144 | * @since Moodle 2.0 | |
9a0df45a | 145 | */ |
146 | function __construct() { | |
147 | parent::__construct('restrictedcontextexception', 'error'); | |
148 | } | |
149 | } | |
150 | ||
151 | /** | |
152 | * Base class for external api methods. | |
4615817d JM |
153 | * |
154 | * @package core_webservice | |
155 | * @copyright 2009 Petr Skodak | |
156 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
157 | * @since Moodle 2.0 | |
9a0df45a | 158 | */ |
159 | class external_api { | |
4615817d JM |
160 | |
161 | /** @var stdClass context where the function calls will be restricted */ | |
9a0df45a | 162 | private static $contextrestriction; |
163 | ||
1bea0c27 | 164 | /** |
23e7b7cc | 165 | * Set context restriction for all following subsequent function calls. |
4615817d JM |
166 | * |
167 | * @param stdClass $context the context restriction | |
168 | * @since Moodle 2.0 | |
1bea0c27 | 169 | */ |
2965d271 | 170 | public static function set_context_restriction($context) { |
9a0df45a | 171 | self::$contextrestriction = $context; |
172 | } | |
173 | ||
2965d271 | 174 | /** |
175 | * This method has to be called before every operation | |
176 | * that takes a longer time to finish! | |
177 | * | |
178 | * @param int $seconds max expected time the next operation needs | |
4615817d | 179 | * @since Moodle 2.0 |
2965d271 | 180 | */ |
181 | public static function set_timeout($seconds=360) { | |
182 | $seconds = ($seconds < 300) ? 300 : $seconds; | |
3ef7279f | 183 | core_php_time_limit::raise($seconds); |
2965d271 | 184 | } |
185 | ||
1bea0c27 | 186 | /** |
c9c5cc81 | 187 | * Validates submitted function parameters, if anything is incorrect |
1bea0c27 | 188 | * invalid_parameter_exception is thrown. |
1d7db36f | 189 | * This is a simple recursive method which is intended to be called from |
190 | * each implementation method of external API. | |
4615817d | 191 | * |
c9c5cc81 | 192 | * @param external_description $description description of parameters |
193 | * @param mixed $params the actual parameters | |
194 | * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found | |
4615817d | 195 | * @since Moodle 2.0 |
1bea0c27 | 196 | */ |
c9c5cc81 | 197 | public static function validate_parameters(external_description $description, $params) { |
04d212ce | 198 | if ($description instanceof external_value) { |
c9c5cc81 | 199 | if (is_array($params) or is_object($params)) { |
93602569 | 200 | throw new invalid_parameter_exception('Scalar type expected, array or object received.'); |
c9c5cc81 | 201 | } |
4f0c6ad1 PS |
202 | |
203 | if ($description->type == PARAM_BOOL) { | |
204 | // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) | |
205 | if (is_bool($params) or $params === 0 or $params === 1 or $params === '0' or $params === '1') { | |
206 | return (bool)$params; | |
207 | } | |
208 | } | |
93602569 JM |
209 | $debuginfo = 'Invalid external api parameter: the value is "' . $params . |
210 | '", the server was expecting "' . $description->type . '" type'; | |
211 | return validate_param($params, $description->type, $description->allownull, $debuginfo); | |
4f0c2d00 | 212 | |
c9c5cc81 | 213 | } else if ($description instanceof external_single_structure) { |
214 | if (!is_array($params)) { | |
93602569 JM |
215 | throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' |
216 | . print_r($params, true) . '\''); | |
c9c5cc81 | 217 | } |
218 | $result = array(); | |
219 | foreach ($description->keys as $key=>$subdesc) { | |
220 | if (!array_key_exists($key, $params)) { | |
382b9cea | 221 | if ($subdesc->required == VALUE_REQUIRED) { |
93602569 | 222 | throw new invalid_parameter_exception('Missing required key in single structure: '. $key); |
c9c5cc81 | 223 | } |
774b1b0f | 224 | if ($subdesc->required == VALUE_DEFAULT) { |
225 | try { | |
226 | $result[$key] = self::validate_parameters($subdesc, $subdesc->default); | |
227 | } catch (invalid_parameter_exception $e) { | |
93602569 JM |
228 | //we are only interested by exceptions returned by validate_param() and validate_parameters() |
229 | //(in order to build the path to the faulty attribut) | |
230 | throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); | |
382b9cea | 231 | } |
774b1b0f | 232 | } |
c9c5cc81 | 233 | } else { |
559a5dbd | 234 | try { |
235 | $result[$key] = self::validate_parameters($subdesc, $params[$key]); | |
236 | } catch (invalid_parameter_exception $e) { | |
93602569 JM |
237 | //we are only interested by exceptions returned by validate_param() and validate_parameters() |
238 | //(in order to build the path to the faulty attribut) | |
239 | throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); | |
559a5dbd | 240 | } |
c9c5cc81 | 241 | } |
242 | unset($params[$key]); | |
243 | } | |
244 | if (!empty($params)) { | |
92fe97f9 | 245 | throw new invalid_parameter_exception('Unexpected keys (' . implode(', ', array_keys($params)) . ') detected in parameter array.'); |
c9c5cc81 | 246 | } |
247 | return $result; | |
1bea0c27 | 248 | |
c9c5cc81 | 249 | } else if ($description instanceof external_multiple_structure) { |
250 | if (!is_array($params)) { | |
93602569 JM |
251 | throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' |
252 | . print_r($params, true) . '\''); | |
c9c5cc81 | 253 | } |
254 | $result = array(); | |
255 | foreach ($params as $param) { | |
256 | $result[] = self::validate_parameters($description->content, $param); | |
257 | } | |
258 | return $result; | |
259 | ||
260 | } else { | |
93602569 | 261 | throw new invalid_parameter_exception('Invalid external api description'); |
c9c5cc81 | 262 | } |
1bea0c27 | 263 | } |
264 | ||
d07ff72d | 265 | /** |
266 | * Clean response | |
23e7b7cc PS |
267 | * If a response attribute is unknown from the description, we just ignore the attribute. |
268 | * If a response attribute is incorrect, invalid_response_exception is thrown. | |
d07ff72d | 269 | * Note: this function is similar to validate parameters, however it is distinct because |
270 | * parameters validation must be distinct from cleaning return values. | |
4615817d | 271 | * |
d07ff72d | 272 | * @param external_description $description description of the return values |
273 | * @param mixed $response the actual response | |
274 | * @return mixed response with added defaults for optional items, invalid_response_exception thrown if any problem found | |
4615817d JM |
275 | * @author 2010 Jerome Mouneyrac |
276 | * @since Moodle 2.0 | |
d07ff72d | 277 | */ |
278 | public static function clean_returnvalue(external_description $description, $response) { | |
279 | if ($description instanceof external_value) { | |
280 | if (is_array($response) or is_object($response)) { | |
93602569 | 281 | throw new invalid_response_exception('Scalar type expected, array or object received.'); |
d07ff72d | 282 | } |
283 | ||
284 | if ($description->type == PARAM_BOOL) { | |
285 | // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) | |
286 | if (is_bool($response) or $response === 0 or $response === 1 or $response === '0' or $response === '1') { | |
287 | return (bool)$response; | |
288 | } | |
289 | } | |
93602569 JM |
290 | $debuginfo = 'Invalid external api response: the value is "' . $response . |
291 | '", the server was expecting "' . $description->type . '" type'; | |
292 | try { | |
293 | return validate_param($response, $description->type, $description->allownull, $debuginfo); | |
294 | } catch (invalid_parameter_exception $e) { | |
295 | //proper exception name, to be recursively catched to build the path to the faulty attribut | |
296 | throw new invalid_response_exception($e->debuginfo); | |
297 | } | |
d07ff72d | 298 | |
299 | } else if ($description instanceof external_single_structure) { | |
9a4c2f50 JM |
300 | if (!is_array($response) && !is_object($response)) { |
301 | throw new invalid_response_exception('Only arrays/objects accepted. The bad value is: \'' . | |
93602569 | 302 | print_r($response, true) . '\''); |
d07ff72d | 303 | } |
9a4c2f50 JM |
304 | |
305 | // Cast objects into arrays. | |
306 | if (is_object($response)) { | |
307 | $response = (array) $response; | |
308 | } | |
309 | ||
d07ff72d | 310 | $result = array(); |
311 | foreach ($description->keys as $key=>$subdesc) { | |
312 | if (!array_key_exists($key, $response)) { | |
313 | if ($subdesc->required == VALUE_REQUIRED) { | |
93602569 | 314 | throw new invalid_response_exception('Error in response - Missing following required key in a single structure: ' . $key); |
d07ff72d | 315 | } |
316 | if ($subdesc instanceof external_value) { | |
143c0abe | 317 | if ($subdesc->required == VALUE_DEFAULT) { |
318 | try { | |
d07ff72d | 319 | $result[$key] = self::clean_returnvalue($subdesc, $subdesc->default); |
93602569 JM |
320 | } catch (invalid_response_exception $e) { |
321 | //build the path to the faulty attribut | |
322 | throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); | |
d07ff72d | 323 | } |
324 | } | |
143c0abe | 325 | } |
d07ff72d | 326 | } else { |
327 | try { | |
328 | $result[$key] = self::clean_returnvalue($subdesc, $response[$key]); | |
93602569 JM |
329 | } catch (invalid_response_exception $e) { |
330 | //build the path to the faulty attribut | |
331 | throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); | |
d07ff72d | 332 | } |
333 | } | |
334 | unset($response[$key]); | |
335 | } | |
336 | ||
337 | return $result; | |
338 | ||
339 | } else if ($description instanceof external_multiple_structure) { | |
340 | if (!is_array($response)) { | |
93602569 JM |
341 | throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' . |
342 | print_r($response, true) . '\''); | |
d07ff72d | 343 | } |
344 | $result = array(); | |
345 | foreach ($response as $param) { | |
346 | $result[] = self::clean_returnvalue($description->content, $param); | |
347 | } | |
348 | return $result; | |
349 | ||
350 | } else { | |
93602569 | 351 | throw new invalid_response_exception('Invalid external api response description'); |
d07ff72d | 352 | } |
353 | } | |
354 | ||
9a0df45a | 355 | /** |
356 | * Makes sure user may execute functions in this context. | |
4615817d JM |
357 | * |
358 | * @param stdClass $context | |
359 | * @since Moodle 2.0 | |
9a0df45a | 360 | */ |
82f0f5e3 | 361 | public static function validate_context($context) { |
4f0c2d00 PS |
362 | global $CFG; |
363 | ||
ab9a01f2 | 364 | if (empty($context)) { |
365 | throw new invalid_parameter_exception('Context does not exist'); | |
366 | } | |
9a0df45a | 367 | if (empty(self::$contextrestriction)) { |
b0c6dc1c | 368 | self::$contextrestriction = context_system::instance(); |
9a0df45a | 369 | } |
370 | $rcontext = self::$contextrestriction; | |
371 | ||
372 | if ($rcontext->contextlevel == $context->contextlevel) { | |
aa7fbebd | 373 | if ($rcontext->id != $context->id) { |
9a0df45a | 374 | throw new restricted_context_exception(); |
375 | } | |
376 | } else if ($rcontext->contextlevel > $context->contextlevel) { | |
377 | throw new restricted_context_exception(); | |
378 | } else { | |
8e8891b7 | 379 | $parents = $context->get_parent_context_ids(); |
9a0df45a | 380 | if (!in_array($rcontext->id, $parents)) { |
381 | throw new restricted_context_exception(); | |
382 | } | |
383 | } | |
384 | ||
385 | if ($context->contextlevel >= CONTEXT_COURSE) { | |
4f0c2d00 | 386 | list($context, $course, $cm) = get_context_info_array($context->id); |
df997f84 | 387 | require_login($course, false, $cm, false, true); |
9a0df45a | 388 | } |
389 | } | |
aac70ffc AA |
390 | |
391 | /** | |
5b23d9ad AA |
392 | * Get context from passed parameters. |
393 | * The passed array must either contain a contextid or a combination of context level and instance id to fetch the context. | |
394 | * For example, the context level can be "course" and instanceid can be courseid. | |
395 | * | |
396 | * See context_helper::get_all_levels() for a list of valid context levels. | |
aac70ffc AA |
397 | * |
398 | * @param array $param | |
399 | * @since Moodle 2.6 | |
400 | * @throws invalid_parameter_exception | |
401 | * @return context | |
402 | */ | |
5b23d9ad | 403 | protected static function get_context_from_params($param) { |
aac70ffc | 404 | $levels = context_helper::get_all_levels(); |
98dece22 | 405 | if (!empty($param['contextid'])) { |
aac70ffc | 406 | return context::instance_by_id($param['contextid'], IGNORE_MISSING); |
eac1383e | 407 | } else if (!empty($param['contextlevel']) && isset($param['instanceid'])) { |
aac70ffc AA |
408 | $contextlevel = "context_".$param['contextlevel']; |
409 | if (!array_search($contextlevel, $levels)) { | |
410 | throw new invalid_parameter_exception('Invalid context level = '.$param['contextlevel']); | |
411 | } | |
412 | return $contextlevel::instance($param['instanceid'], IGNORE_MISSING); | |
413 | } else { | |
414 | // No valid context info was found. | |
415 | throw new invalid_parameter_exception('Missing parameters, please provide either context level with instance id or contextid'); | |
416 | } | |
417 | } | |
9a0df45a | 418 | } |
419 | ||
b038c32c | 420 | /** |
421 | * Common ancestor of all parameter description classes | |
4615817d JM |
422 | * |
423 | * @package core_webservice | |
424 | * @copyright 2009 Petr Skodak | |
425 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
426 | * @since Moodle 2.0 | |
b038c32c | 427 | */ |
428 | abstract class external_description { | |
4615817d | 429 | /** @var string Description of element */ |
b038c32c | 430 | public $desc; |
4615817d JM |
431 | |
432 | /** @var bool Element value required, null not allowed */ | |
b038c32c | 433 | public $required; |
4615817d JM |
434 | |
435 | /** @var mixed Default value */ | |
774b1b0f | 436 | public $default; |
b038c32c | 437 | |
438 | /** | |
439 | * Contructor | |
4615817d | 440 | * |
b038c32c | 441 | * @param string $desc |
442 | * @param bool $required | |
774b1b0f | 443 | * @param mixed $default |
4615817d | 444 | * @since Moodle 2.0 |
b038c32c | 445 | */ |
774b1b0f | 446 | public function __construct($desc, $required, $default) { |
b038c32c | 447 | $this->desc = $desc; |
448 | $this->required = $required; | |
774b1b0f | 449 | $this->default = $default; |
b038c32c | 450 | } |
451 | } | |
452 | ||
453 | /** | |
4615817d JM |
454 | * Scalar value description class |
455 | * | |
456 | * @package core_webservice | |
457 | * @copyright 2009 Petr Skodak | |
458 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
459 | * @since Moodle 2.0 | |
b038c32c | 460 | */ |
04d212ce | 461 | class external_value extends external_description { |
4615817d JM |
462 | |
463 | /** @var mixed Value type PARAM_XX */ | |
b038c32c | 464 | public $type; |
4615817d JM |
465 | |
466 | /** @var bool Allow null values */ | |
b038c32c | 467 | public $allownull; |
468 | ||
469 | /** | |
470 | * Constructor | |
4615817d | 471 | * |
b038c32c | 472 | * @param mixed $type |
473 | * @param string $desc | |
474 | * @param bool $required | |
475 | * @param mixed $default | |
476 | * @param bool $allownull | |
4615817d | 477 | * @since Moodle 2.0 |
b038c32c | 478 | */ |
774b1b0f | 479 | public function __construct($type, $desc='', $required=VALUE_REQUIRED, |
480 | $default=null, $allownull=NULL_ALLOWED) { | |
481 | parent::__construct($desc, $required, $default); | |
78bfb562 | 482 | $this->type = $type; |
b038c32c | 483 | $this->allownull = $allownull; |
484 | } | |
485 | } | |
486 | ||
487 | /** | |
488 | * Associative array description class | |
4615817d JM |
489 | * |
490 | * @package core_webservice | |
491 | * @copyright 2009 Petr Skodak | |
492 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
493 | * @since Moodle 2.0 | |
b038c32c | 494 | */ |
495 | class external_single_structure extends external_description { | |
4615817d JM |
496 | |
497 | /** @var array Description of array keys key=>external_description */ | |
b038c32c | 498 | public $keys; |
499 | ||
500 | /** | |
501 | * Constructor | |
4615817d | 502 | * |
b038c32c | 503 | * @param array $keys |
504 | * @param string $desc | |
505 | * @param bool $required | |
774b1b0f | 506 | * @param array $default |
4615817d | 507 | * @since Moodle 2.0 |
b038c32c | 508 | */ |
774b1b0f | 509 | public function __construct(array $keys, $desc='', |
510 | $required=VALUE_REQUIRED, $default=null) { | |
511 | parent::__construct($desc, $required, $default); | |
b038c32c | 512 | $this->keys = $keys; |
513 | } | |
514 | } | |
515 | ||
516 | /** | |
517 | * Bulk array description class. | |
4615817d JM |
518 | * |
519 | * @package core_webservice | |
520 | * @copyright 2009 Petr Skodak | |
521 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
522 | * @since Moodle 2.0 | |
b038c32c | 523 | */ |
524 | class external_multiple_structure extends external_description { | |
4615817d JM |
525 | |
526 | /** @var external_description content */ | |
b038c32c | 527 | public $content; |
528 | ||
529 | /** | |
530 | * Constructor | |
4615817d | 531 | * |
b038c32c | 532 | * @param external_description $content |
533 | * @param string $desc | |
534 | * @param bool $required | |
774b1b0f | 535 | * @param array $default |
4615817d | 536 | * @since Moodle 2.0 |
b038c32c | 537 | */ |
774b1b0f | 538 | public function __construct(external_description $content, $desc='', |
539 | $required=VALUE_REQUIRED, $default=null) { | |
540 | parent::__construct($desc, $required, $default); | |
b038c32c | 541 | $this->content = $content; |
542 | } | |
543 | } | |
c29cca30 | 544 | |
545 | /** | |
546 | * Description of top level - PHP function parameters. | |
c29cca30 | 547 | * |
4615817d JM |
548 | * @package core_webservice |
549 | * @copyright 2009 Petr Skodak | |
550 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
551 | * @since Moodle 2.0 | |
c29cca30 | 552 | */ |
553 | class external_function_parameters extends external_single_structure { | |
1dc4dc98 DW |
554 | |
555 | /** | |
556 | * Constructor - does extra checking to prevent top level optional parameters. | |
557 | * | |
558 | * @param array $keys | |
559 | * @param string $desc | |
560 | * @param bool $required | |
561 | * @param array $default | |
562 | */ | |
563 | public function __construct(array $keys, $desc='', $required=VALUE_REQUIRED, $default=null) { | |
564 | global $CFG; | |
565 | ||
566 | if ($CFG->debugdeveloper) { | |
567 | foreach ($keys as $key => $value) { | |
568 | if ($value instanceof external_value) { | |
569 | if ($value->required == VALUE_OPTIONAL) { | |
570 | debugging('External function parameters: invalid OPTIONAL value specified.', DEBUG_DEVELOPER); | |
571 | break; | |
572 | } | |
573 | } | |
574 | } | |
575 | } | |
576 | parent::__construct($keys, $desc, $required, $default); | |
577 | } | |
c29cca30 | 578 | } |
2822f40a | 579 | |
4615817d JM |
580 | /** |
581 | * Generate a token | |
582 | * | |
583 | * @param string $tokentype EXTERNAL_TOKEN_EMBEDDED|EXTERNAL_TOKEN_PERMANENT | |
584 | * @param stdClass|int $serviceorid service linked to the token | |
585 | * @param int $userid user linked to the token | |
586 | * @param stdClass|int $contextorid | |
587 | * @param int $validuntil date when the token expired | |
588 | * @param string $iprestriction allowed ip - if 0 or empty then all ips are allowed | |
589 | * @return string generated token | |
590 | * @author 2010 Jamie Pratt | |
591 | * @since Moodle 2.0 | |
592 | */ | |
2822f40a JP |
593 | function external_generate_token($tokentype, $serviceorid, $userid, $contextorid, $validuntil=0, $iprestriction=''){ |
594 | global $DB, $USER; | |
595 | // make sure the token doesn't exist (even if it should be almost impossible with the random generation) | |
596 | $numtries = 0; | |
597 | do { | |
598 | $numtries ++; | |
599 | $generatedtoken = md5(uniqid(rand(),1)); | |
600 | if ($numtries > 5){ | |
601 | throw new moodle_exception('tokengenerationfailed'); | |
602 | } | |
603 | } while ($DB->record_exists('external_tokens', array('token'=>$generatedtoken))); | |
365a5941 | 604 | $newtoken = new stdClass(); |
2822f40a JP |
605 | $newtoken->token = $generatedtoken; |
606 | if (!is_object($serviceorid)){ | |
607 | $service = $DB->get_record('external_services', array('id' => $serviceorid)); | |
608 | } else { | |
609 | $service = $serviceorid; | |
610 | } | |
611 | if (!is_object($contextorid)){ | |
d197ea43 | 612 | $context = context::instance_by_id($contextorid, MUST_EXIST); |
2822f40a JP |
613 | } else { |
614 | $context = $contextorid; | |
615 | } | |
616 | if (empty($service->requiredcapability) || has_capability($service->requiredcapability, $context, $userid)) { | |
617 | $newtoken->externalserviceid = $service->id; | |
618 | } else { | |
619 | throw new moodle_exception('nocapabilitytousethisservice'); | |
620 | } | |
621 | $newtoken->tokentype = $tokentype; | |
622 | $newtoken->userid = $userid; | |
2d0acbd5 JP |
623 | if ($tokentype == EXTERNAL_TOKEN_EMBEDDED){ |
624 | $newtoken->sid = session_id(); | |
625 | } | |
4f0c2d00 PS |
626 | |
627 | $newtoken->contextid = $context->id; | |
2822f40a JP |
628 | $newtoken->creatorid = $USER->id; |
629 | $newtoken->timecreated = time(); | |
630 | $newtoken->validuntil = $validuntil; | |
631 | if (!empty($iprestriction)) { | |
632 | $newtoken->iprestriction = $iprestriction; | |
633 | } | |
634 | $DB->insert_record('external_tokens', $newtoken); | |
635 | return $newtoken->token; | |
2d0acbd5 | 636 | } |
4615817d | 637 | |
2d0acbd5 | 638 | /** |
df997f84 | 639 | * Create and return a session linked token. Token to be used for html embedded client apps that want to communicate |
2d0acbd5 JP |
640 | * with the Moodle server through web services. The token is linked to the current session for the current page request. |
641 | * It is expected this will be called in the script generating the html page that is embedding the client app and that the | |
642 | * returned token will be somehow passed into the client app being embedded in the page. | |
4615817d | 643 | * |
2d0acbd5 JP |
644 | * @param string $servicename name of the web service. Service name as defined in db/services.php |
645 | * @param int $context context within which the web service can operate. | |
646 | * @return int returns token id. | |
4615817d | 647 | * @since Moodle 2.0 |
2d0acbd5 JP |
648 | */ |
649 | function external_create_service_token($servicename, $context){ | |
650 | global $USER, $DB; | |
651 | $service = $DB->get_record('external_services', array('name'=>$servicename), '*', MUST_EXIST); | |
652 | return external_generate_token(EXTERNAL_TOKEN_EMBEDDED, $service, $USER->id, $context, 0); | |
bc81eadb JM |
653 | } |
654 | ||
655 | /** | |
656 | * Delete all pre-built services (+ related tokens) and external functions information defined in the specified component. | |
657 | * | |
658 | * @param string $component name of component (moodle, mod_assignment, etc.) | |
659 | */ | |
660 | function external_delete_descriptions($component) { | |
661 | global $DB; | |
662 | ||
663 | $params = array($component); | |
664 | ||
665 | $DB->delete_records_select('external_tokens', | |
666 | "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params); | |
667 | $DB->delete_records_select('external_services_users', | |
668 | "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params); | |
669 | $DB->delete_records_select('external_services_functions', | |
670 | "functionname IN (SELECT name FROM {external_functions} WHERE component = ?)", $params); | |
671 | $DB->delete_records('external_services', array('component'=>$component)); | |
672 | $DB->delete_records('external_functions', array('component'=>$component)); | |
03d38b92 Y |
673 | } |
674 | ||
675 | /** | |
93ce0e82 | 676 | * Standard Moodle web service warnings |
03d38b92 | 677 | * |
93ce0e82 JM |
678 | * @package core_webservice |
679 | * @copyright 2012 Jerome Mouneyrac | |
680 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
681 | * @since Moodle 2.3 | |
682 | */ | |
683 | class external_warnings extends external_multiple_structure { | |
684 | ||
685 | /** | |
686 | * Constructor | |
687 | * | |
688 | * @since Moodle 2.3 | |
689 | */ | |
8118dbd0 JM |
690 | public function __construct($itemdesc = 'item', $itemiddesc = 'item id', |
691 | $warningcodedesc = 'the warning code can be used by the client app to implement specific behaviour') { | |
93ce0e82 JM |
692 | |
693 | parent::__construct( | |
694 | new external_single_structure( | |
695 | array( | |
8118dbd0 JM |
696 | 'item' => new external_value(PARAM_TEXT, $itemdesc, VALUE_OPTIONAL), |
697 | 'itemid' => new external_value(PARAM_INT, $itemiddesc, VALUE_OPTIONAL), | |
698 | 'warningcode' => new external_value(PARAM_ALPHANUM, $warningcodedesc), | |
93ce0e82 JM |
699 | 'message' => new external_value(PARAM_TEXT, |
700 | 'untranslated english message to explain the warning') | |
701 | ), 'warning'), | |
702 | 'list of warnings', VALUE_OPTIONAL); | |
703 | } | |
704 | } | |
705 | ||
706 | /** | |
707 | * A pre-filled external_value class for text format. | |
708 | * | |
709 | * Default is FORMAT_HTML | |
710 | * This should be used all the time in external xxx_params()/xxx_returns functions | |
711 | * as it is the standard way to implement text format param/return values. | |
712 | * | |
713 | * @package core_webservice | |
714 | * @copyright 2012 Jerome Mouneyrac | |
715 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
716 | * @since Moodle 2.3 | |
03d38b92 | 717 | */ |
93ce0e82 JM |
718 | class external_format_value extends external_value { |
719 | ||
720 | /** | |
721 | * Constructor | |
722 | * | |
723 | * @param string $textfieldname Name of the text field | |
724 | * @param int $required if VALUE_REQUIRED then set standard default FORMAT_HTML | |
725 | * @since Moodle 2.3 | |
726 | */ | |
727 | public function __construct($textfieldname, $required = VALUE_REQUIRED) { | |
728 | ||
729 | $default = ($required == VALUE_DEFAULT) ? FORMAT_HTML : null; | |
730 | ||
731 | $desc = $textfieldname . ' format (' . FORMAT_HTML . ' = HTML, ' | |
732 | . FORMAT_MOODLE . ' = MOODLE, ' | |
733 | . FORMAT_PLAIN . ' = PLAIN or ' | |
734 | . FORMAT_MARKDOWN . ' = MARKDOWN)'; | |
735 | ||
55e168e3 | 736 | parent::__construct(PARAM_INT, $desc, $required, $default); |
93ce0e82 JM |
737 | } |
738 | } | |
739 | ||
740 | /** | |
741 | * Validate text field format against known FORMAT_XXX | |
742 | * | |
743 | * @param array $format the format to validate | |
744 | * @return the validated format | |
745 | * @throws coding_exception | |
5bcfd504 | 746 | * @since Moodle 2.3 |
93ce0e82 JM |
747 | */ |
748 | function external_validate_format($format) { | |
749 | $allowedformats = array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN); | |
750 | if (!in_array($format, $allowedformats)) { | |
751 | throw new moodle_exception('formatnotsupported', 'webservice', '' , null, | |
752 | 'The format with value=' . $format . ' is not supported by this Moodle site'); | |
753 | } | |
754 | return $format; | |
755 | } | |
756 | ||
9764aab9 DW |
757 | /** |
758 | * Format the string to be returned properly as requested by the either the web service server, | |
759 | * either by an internally call. | |
760 | * The caller can change the format (raw) with the external_settings singleton | |
761 | * All web service servers must set this singleton when parsing the $_GET and $_POST. | |
762 | * | |
763 | * @param string $str The string to be filtered. Should be plain text, expect | |
764 | * possibly for multilang tags. | |
765 | * @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713 | |
766 | * @param int $contextid The id of the context for the string (affects filters). | |
767 | * @param array $options options array/object or courseid | |
768 | * @return string text | |
769 | * @since Moodle 3.0 | |
770 | */ | |
771 | function external_format_string($str, $contextid, $striplinks = true, $options = array()) { | |
772 | ||
773 | // Get settings (singleton). | |
774 | $settings = external_settings::get_instance(); | |
775 | if (empty($contextid)) { | |
776 | throw new coding_exception('contextid is required'); | |
777 | } | |
778 | ||
779 | if (!$settings->get_raw()) { | |
780 | $context = context::instance_by_id($contextid); | |
781 | $options['context'] = $context; | |
08bda175 | 782 | $options['filter'] = $settings->get_filter(); |
9764aab9 DW |
783 | $str = format_string($str, $striplinks, $options); |
784 | } | |
785 | ||
786 | return $str; | |
787 | } | |
788 | ||
93ce0e82 JM |
789 | /** |
790 | * Format the text to be returned properly as requested by the either the web service server, | |
791 | * either by an internally call. | |
792 | * The caller can change the format (raw, filter, file, fileurl) with the external_settings singleton | |
793 | * All web service servers must set this singleton when parsing the $_GET and $_POST. | |
794 | * | |
795 | * @param string $text The content that may contain ULRs in need of rewriting. | |
c4a15021 | 796 | * @param int $textformat The text format. |
93ce0e82 JM |
797 | * @param int $contextid This parameter and the next two identify the file area to use. |
798 | * @param string $component | |
799 | * @param string $filearea helps identify the file area. | |
800 | * @param int $itemid helps identify the file area. | |
801 | * @return array text + textformat | |
802 | * @since Moodle 2.3 | |
803 | */ | |
804 | function external_format_text($text, $textformat, $contextid, $component, $filearea, $itemid) { | |
805 | global $CFG; | |
806 | ||
807 | // Get settings (singleton). | |
808 | $settings = external_settings::get_instance(); | |
809 | ||
810 | if ($settings->get_fileurl()) { | |
ea29059e | 811 | require_once($CFG->libdir . "/filelib.php"); |
93ce0e82 JM |
812 | $text = file_rewrite_pluginfile_urls($text, $settings->get_file(), $contextid, $component, $filearea, $itemid); |
813 | } | |
814 | ||
815 | if (!$settings->get_raw()) { | |
9764aab9 DW |
816 | $context = context::instance_by_id($contextid); |
817 | $text = format_text($text, $textformat, array('para' => false, 'filter' => $settings->get_filter(), 'context' => $context)); | |
c4a15021 | 818 | $textformat = FORMAT_HTML; // Once converted to html (from markdown, plain... lets inform consumer this is already HTML). |
93ce0e82 JM |
819 | } |
820 | ||
821 | return array($text, $textformat); | |
822 | } | |
823 | ||
824 | /** | |
825 | * Singleton to handle the external settings. | |
826 | * | |
827 | * We use singleton to encapsulate the "logic" | |
828 | * | |
829 | * @package core_webservice | |
830 | * @copyright 2012 Jerome Mouneyrac | |
831 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
832 | * @since Moodle 2.3 | |
833 | */ | |
834 | class external_settings { | |
835 | ||
836 | /** @var object the singleton instance */ | |
837 | public static $instance = null; | |
838 | ||
839 | /** @var boolean Should the external function return raw text or formatted */ | |
840 | private $raw = false; | |
841 | ||
842 | /** @var boolean Should the external function filter the text */ | |
843 | private $filter = false; | |
844 | ||
845 | /** @var boolean Should the external function rewrite plugin file url */ | |
846 | private $fileurl = true; | |
847 | ||
848 | /** @var string In which file should the urls be rewritten */ | |
849 | private $file = 'webservice/pluginfile.php'; | |
850 | ||
851 | /** | |
852 | * Constructor - protected - can not be instanciated | |
853 | */ | |
854 | protected function __construct() { | |
9764aab9 DW |
855 | if (!defined('AJAX_SCRIPT') && !defined('CLI_SCRIPT') && !defined('WS_SERVER')) { |
856 | // For normal pages, the default should match the default for format_text. | |
857 | $this->filter = true; | |
858 | } | |
93ce0e82 JM |
859 | } |
860 | ||
861 | /** | |
862 | * Clone - private - can not be cloned | |
863 | */ | |
864 | private final function __clone() { | |
865 | } | |
866 | ||
867 | /** | |
868 | * Return only one instance | |
869 | * | |
870 | * @return object | |
871 | */ | |
872 | public static function get_instance() { | |
873 | if (self::$instance === null) { | |
874 | self::$instance = new external_settings; | |
875 | } | |
876 | ||
877 | return self::$instance; | |
878 | } | |
879 | ||
880 | /** | |
881 | * Set raw | |
882 | * | |
883 | * @param boolean $raw | |
884 | */ | |
885 | public function set_raw($raw) { | |
886 | $this->raw = $raw; | |
887 | } | |
888 | ||
889 | /** | |
890 | * Get raw | |
891 | * | |
892 | * @return boolean | |
893 | */ | |
894 | public function get_raw() { | |
895 | return $this->raw; | |
896 | } | |
897 | ||
898 | /** | |
899 | * Set filter | |
900 | * | |
901 | * @param boolean $filter | |
902 | */ | |
903 | public function set_filter($filter) { | |
904 | $this->filter = $filter; | |
905 | } | |
906 | ||
907 | /** | |
908 | * Get filter | |
909 | * | |
910 | * @return boolean | |
911 | */ | |
912 | public function get_filter() { | |
913 | return $this->filter; | |
914 | } | |
915 | ||
916 | /** | |
917 | * Set fileurl | |
918 | * | |
919 | * @param boolean $fileurl | |
920 | */ | |
921 | public function set_fileurl($fileurl) { | |
922 | $this->fileurl = $fileurl; | |
923 | } | |
924 | ||
925 | /** | |
926 | * Get fileurl | |
927 | * | |
928 | * @return boolean | |
929 | */ | |
930 | public function get_fileurl() { | |
931 | return $this->fileurl; | |
932 | } | |
933 | ||
934 | /** | |
935 | * Set file | |
936 | * | |
937 | * @param string $file | |
938 | */ | |
939 | public function set_file($file) { | |
940 | $this->file = $file; | |
941 | } | |
942 | ||
943 | /** | |
944 | * Get file | |
945 | * | |
946 | * @return string | |
947 | */ | |
948 | public function get_file() { | |
949 | return $this->file; | |
950 | } | |
50f9449f | 951 | } |
82f0f5e3 JL |
952 | |
953 | /** | |
954 | * Utility functions for the external API. | |
955 | * | |
956 | * @package core_webservice | |
957 | * @copyright 2015 Juan Leyva | |
958 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
959 | * @since Moodle 3.0 | |
960 | */ | |
961 | class external_util { | |
962 | ||
963 | /** | |
964 | * Validate a list of courses, returning the complete course objects for valid courses. | |
965 | * | |
966 | * @param array $courseids A list of course ids | |
b80ede13 | 967 | * @param array $courses An array of courses already pre-fetched, indexed by course id. |
82f0f5e3 JL |
968 | * @return array An array of courses and the validation warnings |
969 | */ | |
4b11af96 | 970 | public static function validate_courses($courseids, $courses = array()) { |
82f0f5e3 JL |
971 | // Delete duplicates. |
972 | $courseids = array_unique($courseids); | |
82f0f5e3 JL |
973 | $warnings = array(); |
974 | ||
4b11af96 FM |
975 | // Remove courses which are not even requested. |
976 | $courses = array_intersect_key($courses, array_flip($courseids)); | |
977 | ||
82f0f5e3 JL |
978 | foreach ($courseids as $cid) { |
979 | // Check the user can function in this context. | |
980 | try { | |
981 | $context = context_course::instance($cid); | |
982 | external_api::validate_context($context); | |
4b11af96 FM |
983 | |
984 | if (!isset($courses[$cid])) { | |
985 | $courses[$cid] = get_course($cid); | |
986 | } | |
82f0f5e3 | 987 | } catch (Exception $e) { |
4b11af96 | 988 | unset($courses[$cid]); |
82f0f5e3 JL |
989 | $warnings[] = array( |
990 | 'item' => 'course', | |
991 | 'itemid' => $cid, | |
992 | 'warningcode' => '1', | |
993 | 'message' => 'No access rights in course context' | |
994 | ); | |
995 | } | |
996 | } | |
997 | ||
998 | return array($courses, $warnings); | |
999 | } | |
1000 | ||
1001 | } |