|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00027 defined('MOODLE_INTERNAL') || die(); 00028 00036 function external_function_info($function, $strictness=MUST_EXIST) { 00037 global $DB, $CFG; 00038 00039 if (!is_object($function)) { 00040 if (!$function = $DB->get_record('external_functions', array('name'=>$function), '*', $strictness)) { 00041 return false; 00042 } 00043 } 00044 00045 //first find and include the ext implementation class 00046 $function->classpath = empty($function->classpath) ? get_component_directory($function->component).'/externallib.php' : $CFG->dirroot.'/'.$function->classpath; 00047 if (!file_exists($function->classpath)) { 00048 throw new coding_exception('Can not find file with external function implementation'); 00049 } 00050 require_once($function->classpath); 00051 00052 $function->parameters_method = $function->methodname.'_parameters'; 00053 $function->returns_method = $function->methodname.'_returns'; 00054 00055 // make sure the implementaion class is ok 00056 if (!method_exists($function->classname, $function->methodname)) { 00057 throw new coding_exception('Missing implementation method of '.$function->classname.'::'.$function->methodname); 00058 } 00059 if (!method_exists($function->classname, $function->parameters_method)) { 00060 throw new coding_exception('Missing parameters description'); 00061 } 00062 if (!method_exists($function->classname, $function->returns_method)) { 00063 throw new coding_exception('Missing returned values description'); 00064 } 00065 00066 // fetch the parameters description 00067 $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method)); 00068 if (!($function->parameters_desc instanceof external_function_parameters)) { 00069 throw new coding_exception('Invalid parameters description'); 00070 } 00071 00072 // fetch the return values description 00073 $function->returns_desc = call_user_func(array($function->classname, $function->returns_method)); 00074 // null means void result or result is ignored 00075 if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) { 00076 throw new coding_exception('Invalid return description'); 00077 } 00078 00079 //now get the function description 00080 //TODO: use localised lang pack descriptions, it would be nice to have 00081 // easy to understand descriptions in admin UI, 00082 // on the other hand this is still a bit in a flux and we need to find some new naming 00083 // conventions for these descriptions in lang packs 00084 $function->description = null; 00085 $servicesfile = get_component_directory($function->component).'/db/services.php'; 00086 if (file_exists($servicesfile)) { 00087 $functions = null; 00088 include($servicesfile); 00089 if (isset($functions[$function->name]['description'])) { 00090 $function->description = $functions[$function->name]['description']; 00091 } 00092 if (isset($functions[$function->name]['testclientpath'])) { 00093 $function->testclientpath = $functions[$function->name]['testclientpath']; 00094 } 00095 } 00096 00097 return $function; 00098 } 00099 00104 class restricted_context_exception extends moodle_exception { 00108 function __construct() { 00109 parent::__construct('restrictedcontextexception', 'error'); 00110 } 00111 } 00112 00116 class external_api { 00117 private static $contextrestriction; 00118 00124 public static function set_context_restriction($context) { 00125 self::$contextrestriction = $context; 00126 } 00127 00135 public static function set_timeout($seconds=360) { 00136 $seconds = ($seconds < 300) ? 300 : $seconds; 00137 set_time_limit($seconds); 00138 } 00139 00149 public static function validate_parameters(external_description $description, $params) { 00150 if ($description instanceof external_value) { 00151 if (is_array($params) or is_object($params)) { 00152 throw new invalid_parameter_exception('Scalar type expected, array or object received.'); 00153 } 00154 00155 if ($description->type == PARAM_BOOL) { 00156 // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) 00157 if (is_bool($params) or $params === 0 or $params === 1 or $params === '0' or $params === '1') { 00158 return (bool)$params; 00159 } 00160 } 00161 $debuginfo = 'Invalid external api parameter: the value is "' . $params . 00162 '", the server was expecting "' . $description->type . '" type'; 00163 return validate_param($params, $description->type, $description->allownull, $debuginfo); 00164 00165 } else if ($description instanceof external_single_structure) { 00166 if (!is_array($params)) { 00167 throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' 00168 . print_r($params, true) . '\''); 00169 } 00170 $result = array(); 00171 foreach ($description->keys as $key=>$subdesc) { 00172 if (!array_key_exists($key, $params)) { 00173 if ($subdesc->required == VALUE_REQUIRED) { 00174 throw new invalid_parameter_exception('Missing required key in single structure: '. $key); 00175 } 00176 if ($subdesc->required == VALUE_DEFAULT) { 00177 try { 00178 $result[$key] = self::validate_parameters($subdesc, $subdesc->default); 00179 } catch (invalid_parameter_exception $e) { 00180 //we are only interested by exceptions returned by validate_param() and validate_parameters() 00181 //(in order to build the path to the faulty attribut) 00182 throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); 00183 } 00184 } 00185 } else { 00186 try { 00187 $result[$key] = self::validate_parameters($subdesc, $params[$key]); 00188 } catch (invalid_parameter_exception $e) { 00189 //we are only interested by exceptions returned by validate_param() and validate_parameters() 00190 //(in order to build the path to the faulty attribut) 00191 throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo); 00192 } 00193 } 00194 unset($params[$key]); 00195 } 00196 if (!empty($params)) { 00197 throw new invalid_parameter_exception('Unexpected keys (' . implode(', ', array_keys($params)) . ') detected in parameter array.'); 00198 } 00199 return $result; 00200 00201 } else if ($description instanceof external_multiple_structure) { 00202 if (!is_array($params)) { 00203 throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \'' 00204 . print_r($params, true) . '\''); 00205 } 00206 $result = array(); 00207 foreach ($params as $param) { 00208 $result[] = self::validate_parameters($description->content, $param); 00209 } 00210 return $result; 00211 00212 } else { 00213 throw new invalid_parameter_exception('Invalid external api description'); 00214 } 00215 } 00216 00227 public static function clean_returnvalue(external_description $description, $response) { 00228 if ($description instanceof external_value) { 00229 if (is_array($response) or is_object($response)) { 00230 throw new invalid_response_exception('Scalar type expected, array or object received.'); 00231 } 00232 00233 if ($description->type == PARAM_BOOL) { 00234 // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-) 00235 if (is_bool($response) or $response === 0 or $response === 1 or $response === '0' or $response === '1') { 00236 return (bool)$response; 00237 } 00238 } 00239 $debuginfo = 'Invalid external api response: the value is "' . $response . 00240 '", the server was expecting "' . $description->type . '" type'; 00241 try { 00242 return validate_param($response, $description->type, $description->allownull, $debuginfo); 00243 } catch (invalid_parameter_exception $e) { 00244 //proper exception name, to be recursively catched to build the path to the faulty attribut 00245 throw new invalid_response_exception($e->debuginfo); 00246 } 00247 00248 } else if ($description instanceof external_single_structure) { 00249 if (!is_array($response)) { 00250 throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' . 00251 print_r($response, true) . '\''); 00252 } 00253 $result = array(); 00254 foreach ($description->keys as $key=>$subdesc) { 00255 if (!array_key_exists($key, $response)) { 00256 if ($subdesc->required == VALUE_REQUIRED) { 00257 throw new invalid_response_exception('Error in response - Missing following required key in a single structure: ' . $key); 00258 } 00259 if ($subdesc instanceof external_value) { 00260 if ($subdesc->required == VALUE_DEFAULT) { 00261 try { 00262 $result[$key] = self::clean_returnvalue($subdesc, $subdesc->default); 00263 } catch (invalid_response_exception $e) { 00264 //build the path to the faulty attribut 00265 throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); 00266 } 00267 } 00268 } 00269 } else { 00270 try { 00271 $result[$key] = self::clean_returnvalue($subdesc, $response[$key]); 00272 } catch (invalid_response_exception $e) { 00273 //build the path to the faulty attribut 00274 throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo); 00275 } 00276 } 00277 unset($response[$key]); 00278 } 00279 00280 return $result; 00281 00282 } else if ($description instanceof external_multiple_structure) { 00283 if (!is_array($response)) { 00284 throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' . 00285 print_r($response, true) . '\''); 00286 } 00287 $result = array(); 00288 foreach ($response as $param) { 00289 $result[] = self::clean_returnvalue($description->content, $param); 00290 } 00291 return $result; 00292 00293 } else { 00294 throw new invalid_response_exception('Invalid external api response description'); 00295 } 00296 } 00297 00303 protected static function validate_context($context) { 00304 global $CFG; 00305 00306 if (empty($context)) { 00307 throw new invalid_parameter_exception('Context does not exist'); 00308 } 00309 if (empty(self::$contextrestriction)) { 00310 self::$contextrestriction = get_context_instance(CONTEXT_SYSTEM); 00311 } 00312 $rcontext = self::$contextrestriction; 00313 00314 if ($rcontext->contextlevel == $context->contextlevel) { 00315 if ($rcontext->id != $context->id) { 00316 throw new restricted_context_exception(); 00317 } 00318 } else if ($rcontext->contextlevel > $context->contextlevel) { 00319 throw new restricted_context_exception(); 00320 } else { 00321 $parents = get_parent_contexts($context); 00322 if (!in_array($rcontext->id, $parents)) { 00323 throw new restricted_context_exception(); 00324 } 00325 } 00326 00327 if ($context->contextlevel >= CONTEXT_COURSE) { 00328 list($context, $course, $cm) = get_context_info_array($context->id); 00329 require_login($course, false, $cm, false, true); 00330 } 00331 } 00332 } 00333 00337 abstract class external_description { 00339 public $desc; 00341 public $required; 00343 public $default; 00344 00351 public function __construct($desc, $required, $default) { 00352 $this->desc = $desc; 00353 $this->required = $required; 00354 $this->default = $default; 00355 } 00356 } 00357 00361 class external_value extends external_description { 00363 public $type; 00365 public $allownull; 00366 00375 public function __construct($type, $desc='', $required=VALUE_REQUIRED, 00376 $default=null, $allownull=NULL_ALLOWED) { 00377 parent::__construct($desc, $required, $default); 00378 $this->type = $type; 00379 $this->allownull = $allownull; 00380 } 00381 } 00382 00386 class external_single_structure extends external_description { 00388 public $keys; 00389 00397 public function __construct(array $keys, $desc='', 00398 $required=VALUE_REQUIRED, $default=null) { 00399 parent::__construct($desc, $required, $default); 00400 $this->keys = $keys; 00401 } 00402 } 00403 00407 class external_multiple_structure extends external_description { 00409 public $content; 00410 00418 public function __construct(external_description $content, $desc='', 00419 $required=VALUE_REQUIRED, $default=null) { 00420 parent::__construct($desc, $required, $default); 00421 $this->content = $content; 00422 } 00423 } 00424 00430 class external_function_parameters extends external_single_structure { 00431 } 00432 00433 function external_generate_token($tokentype, $serviceorid, $userid, $contextorid, $validuntil=0, $iprestriction=''){ 00434 global $DB, $USER; 00435 // make sure the token doesn't exist (even if it should be almost impossible with the random generation) 00436 $numtries = 0; 00437 do { 00438 $numtries ++; 00439 $generatedtoken = md5(uniqid(rand(),1)); 00440 if ($numtries > 5){ 00441 throw new moodle_exception('tokengenerationfailed'); 00442 } 00443 } while ($DB->record_exists('external_tokens', array('token'=>$generatedtoken))); 00444 $newtoken = new stdClass(); 00445 $newtoken->token = $generatedtoken; 00446 if (!is_object($serviceorid)){ 00447 $service = $DB->get_record('external_services', array('id' => $serviceorid)); 00448 } else { 00449 $service = $serviceorid; 00450 } 00451 if (!is_object($contextorid)){ 00452 $context = get_context_instance_by_id($contextorid, MUST_EXIST); 00453 } else { 00454 $context = $contextorid; 00455 } 00456 if (empty($service->requiredcapability) || has_capability($service->requiredcapability, $context, $userid)) { 00457 $newtoken->externalserviceid = $service->id; 00458 } else { 00459 throw new moodle_exception('nocapabilitytousethisservice'); 00460 } 00461 $newtoken->tokentype = $tokentype; 00462 $newtoken->userid = $userid; 00463 if ($tokentype == EXTERNAL_TOKEN_EMBEDDED){ 00464 $newtoken->sid = session_id(); 00465 } 00466 00467 $newtoken->contextid = $context->id; 00468 $newtoken->creatorid = $USER->id; 00469 $newtoken->timecreated = time(); 00470 $newtoken->validuntil = $validuntil; 00471 if (!empty($iprestriction)) { 00472 $newtoken->iprestriction = $iprestriction; 00473 } 00474 $DB->insert_record('external_tokens', $newtoken); 00475 return $newtoken->token; 00476 } 00486 function external_create_service_token($servicename, $context){ 00487 global $USER, $DB; 00488 $service = $DB->get_record('external_services', array('name'=>$servicename), '*', MUST_EXIST); 00489 return external_generate_token(EXTERNAL_TOKEN_EMBEDDED, $service, $USER->id, $context, 0); 00490 }