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