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